\n 778 benchmark-demo`\r\n2. 所有以BenchmarkNew开始的方法:`go test -bench='^BenchmarkNew' benchmark-demo`\r\n## 3.2. 指定cpu\r\n我们仔细观察[[benchmark#1. 执行benchmark测试用例|步骤一]]的输出:\r\nBenchmarkFib-12 中的 `-12` 即 `GOMAXPROCS`,默认等于CPU核数。可以通过`-cpu`参数改变`GOMAXPROCS`,`-cpu` 支持传入一个列表作为参数,例如:\r\n```shell\r\ngo test -bench . -cpu=1 \r\ngoos: windows\r\ngoarch: amd64\r\npkg: go_test\r\ncpu: AMD Ryzen 5 3600 6-Core Processor\r\nBenchmarkFib-1 238 4724139 ns/op\r\nPASS\r\nok go_test 1.915s\r\n```\r\n在上面的例子中,改变CPU的核数对结果几乎没有影响,因为这个Fib的调用是串行的,不涉及并发编程,改造测试用例如下:\r\n```go\r\npackage test\r\n\r\nimport (\r\n\t\"sync\"\r\n\t\"testing\"\r\n)\r\n\r\nfunc fib(n int) int {\r\n\tif n == 0 || n == 1 {\r\n\t\treturn n\r\n\t}\r\n\treturn fib(n-2) + fib(n-1)\r\n}\r\n\r\nfunc fib_sum(index int) int {\r\n\tcount := 0\r\n\twg := sync.WaitGroup{}\r\n\tlock := sync.Mutex{}\r\n\t// 算10遍\r\n\tfor i := 0; i < 10; i++ {\r\n\t\twg.Add(1)\r\n\t\tgo func() {\r\n\t\t\tsum := fib(index)\r\n\t\t\tlock.Lock()\r\n\t\t\tcount += sum\r\n\t\t\tlock.Unlock()\r\n\t\t\twg.Done()\r\n\t\t}()\r\n\t}\r\n\twg.Wait()\r\n\treturn count\r\n}\r\n\r\nfunc BenchmarkFib(b *testing.B) {\r\n\tfor i := 0; i < b.N; i++ {\r\n\t\tfib_sum(30)\r\n\t}\r\n}\r\n```\r\n可以看出在使用了并发编程后,10个cpu的运行效率比单个cpu运行效率明显提高了。\r\n```shell\r\ngo test -bench . -cpu=1 \r\ngoos: darwin\r\ngoarch: amd64\r\npkg: go_test/test\r\ncpu: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz\r\nBenchmarkFib 21 53187164 ns/op\r\nPASS\r\nok go_test/test 1.745s\r\n\r\ngo test -bench . -cpu=10\r\ngoos: darwin\r\ngoarch: amd64\r\npkg: go_test/test\r\ncpu: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz\r\nBenchmarkFib-10 76 15448673 ns/op\r\nPASS\r\nok go_test/test 1.785s\r\n```\r\n## 3.3. 测试时间与测试轮数\r\n对于性能测试来说,提升测试准确度的一个重要手段就是增加测试的次数。我们可以使用 `-benchtime` 和 `-count` 两个参数达到这个目的。\r\n### 3.3.1. benchtime\r\nbenchmark 的默认时间是 1s,那么我们可以使用 `-benchtime` 指定为 5s。例如:\r\n```go\r\ngo test -bench . -benchtime=5s\r\ngoos: windows\r\ngoarch: amd64\r\npkg: go_test\r\ncpu: AMD Ryzen 5 3600 6-Core Processor\r\nBenchmarkFib-12 1201 4876905 ns/op\r\nPASS\r\nok go_test 6.615s\r\n```\r\n将 `-benchtime` 设置为 5s,用例执行次数也变成了原来的5倍,每次函数调用时间仍为 04.8ms作用,几乎没有变化(我们这个测试用例的执行与b.N没有关系)。\r\n\r\n`-benchtime` 的值除了是时间外,还可以是具体的次数。例如,执行 30 次可以用 `-benchtime=30x`:\r\n```go\r\ngo test -bench . -benchtime=30x\r\ngoos: windows\r\ngoarch: amd64\r\npkg: go_test\r\ncpu: AMD Ryzen 5 3600 6-Core Processor\r\nBenchmarkFib-12 30 4639033 ns/op\r\nPASS\r\nok go_test 0.417s\r\n\r\n```\r\n### 3.3.2. count\r\n`-count` 参数可以用来设置 benchmark 的轮数。例如,进行3轮benchmark。\r\n```shell\r\ngo test -bench . -benchtime=30x -count=3\r\ngoos: windows\r\ngoarch: amd64\r\npkg: go_test\r\ncpu: AMD Ryzen 5 3600 6-Core Processor\r\nBenchmarkFib-12 30 4907513 ns/op\r\nBenchmarkFib-12 30 4996390 ns/op\r\nBenchmarkFib-12 30 5041850 ns/op\r\nPASS\r\nok go_test 0.734s\r\n```\r\n# 4. 内存分配测试\r\n`-benchmem` 参数可以度量内存分配的次数。内存分配次数也性能也是息息相关的,例如不合理的切片容量,将导致内存重新分配,带来不必要的开销。\r\n\r\n在下面的例子中,`generateWithCap` 和 `generate` 的作用是一致的,生成一组长度为 n 的随机序列。唯一的不同在于,`generateWithCap` 创建切片时,将切片的容量(capacity)设置为n,这样切片就会一次性申请n个整数所需的内存。\r\n```go\r\npackage Generate\r\n\r\nimport (\r\n\t\"math/rand\"\r\n\t\"testing\"\r\n)\r\n\r\nfunc generateWithCap(n int) []int {\r\n\tnums := make([]int, 0, n)\r\n\tfor i := 0; i < n; i++ {\r\n\t\tnums = append(nums, rand.Int())\r\n\t}\r\n\treturn nums\r\n}\r\n\r\nfunc generate(n int) []int {\r\n\tnums := make([]int, 0)\r\n\tfor i := 0; i < n; i++ {\r\n\t\tnums = append(nums, rand.Int())\r\n\t}\r\n\treturn nums\r\n}\r\n\r\nfunc BenchmarkGenerateWithCap(b *testing.B) {\r\n\tfor n := 0; n < b.N; n++ {\r\n\t\tgenerateWithCap(1000000)\r\n\t}\r\n}\r\n\r\nfunc BenchmarkGenerate(b *testing.B) {\r\n\tfor n := 0; n < b.N; n++ {\r\n\t\tgenerate(1000000)\r\n\t}\r\n}\r\n```\r\n运行该用例的结果是:\r\n```shell\r\ngo test -bench='Generate' .\r\ngoos: windows\r\ngoarch: amd64\r\npkg: go_test/Generate\r\ncpu: AMD Ryzen 5 3600 6-Core Processor\r\nBenchmarkGenerateWithCap\r\nBenchmarkGenerateWithCap-12 88 12633669 ns/op\r\nBenchmarkGenerate\r\nBenchmarkGenerate-12 62 17817368 ns/op\r\nPASS\r\n```\r\n可以看到生成100w个数字的随机序列,`GenerateWithCap`的耗时比 `Generate`少很多。我们可以使用 `-benchmem` 参数看到内存分配的情况:\r\n```shell\r\ngo test -bench . -benchmem \r\ngoos: windows\r\ngoarch: amd64\r\npkg: go_test/Generate\r\ncpu: AMD Ryzen 5 3600 6-Core Processor\r\nBenchmarkGenerateWithCap-12 98 12146879 ns/op 8003645 B/op 1 allocs/op\r\nBenchmarkGenerate-12 58 17856278 ns/op 41678169 B/op 38 allocs/op\r\nPASS\r\nok go_test/Generate 2.459s\r\n```\r\n`Generate`分配的内存是`GenerateWithCap`的5倍,设置了切片容量,内存只分配一次,而不设置切片容量,内存分配了 40 次。\r\n# 5. 测试函数时间和空间复杂度\r\n不同的函数复杂度不同,O(1),O(n),O(n^2) 等,利用benchmark验证复杂度一个简单的方式,是构造不同的输入。对刚才的 benchmark 稍作改造,便能够达到目的。\r\n\r\n对刚刚的例子进行稍加改造\r\n```go\r\npackage main \r\n \r\nimport ( \r\n\t\"math/rand\" \r\n\t\"testing\" \r\n\t\"time\" \r\n) \r\n \r\nfunc generate(n int) []int { \r\n\trand.Seed(time.Now().UnixNano()) \r\n\tnums := make([]int, 0) \r\n\tfor i := 0; i < n; i++ { \r\n\t\tnums = append(nums, rand.Int()) \r\n\t} \r\n\treturn nums \r\n} \r\nfunc benchmarkGenerate(i int, b *testing.B) { \r\n\tfor n := 0; n < b.N; n++ { \r\n\t\tgenerate(i) \r\n\t} \r\n} \r\n \r\nfunc BenchmarkGenerate1000(b *testing.B) { benchmarkGenerate(1000, b) } \r\nfunc BenchmarkGenerate10000(b *testing.B) { benchmarkGenerate(10000, b) } \r\nfunc BenchmarkGenerate100000(b *testing.B) { benchmarkGenerate(100000, b) } \r\nfunc BenchmarkGenerate1000000(b *testing.B) { benchmarkGenerate(1000000, b) }\r\n```\r\n可以看到时间复杂度和空间复杂度都是线性增长的,说明时间和空间复杂度都是`o(n)`\r\n```shell\r\ngo test -bench . -benchmem\r\ngoos: windows\r\ngoarch: amd64\r\npkg: go_test/Generate\r\ncpu: AMD Ryzen 5 3600 6-Core Processor\r\nBenchmarkGenerate1000-12 49246 23597 ns/op 25208 B/op 12 allocs/op\r\nBenchmarkGenerate10000-12 7395 169348 ns/op 357625 B/op 19 allocs/op\r\nBenchmarkGenerate100000-12 646 1771936 ns/op 4101387 B/op 28 allocs/op\r\nBenchmarkGenerate1000000-12 66 18853998 ns/op 41678114 B/op 38 allocs/op\r\nPASS\r\nok go_test/Generate 6.410s\r\n```\r\n# 6. 控制计时\r\n- ResetTimer,如果在benchmark开始前,需要一些准备工作,如果准备工作比较耗时,则需要将这部分代码的耗时忽略掉,则可以在需要计时之前调用ResetTimer来重制计时\r\n- StopTimer&StartTimer,如果在benchmark执行过程中,有些操作比较耗时,且不想参与计算,可以通过Stop&Start的方式,忽略掉中间部分的耗时。\r\n# 7. 并行测试\r\n前面的BenchmarkFib是常规的串行测试,如果被测试的方法在真实环境中存在并发调用,那么在基准测试中也应该通过并行测试来了解其基本性能(例如锁造成的阻塞)\r\n```go\r\npackage main\r\n\r\nimport \"testing\"\r\n\r\nfunc BenchmarkParallelFib(b *testing.B) {\r\n\tb.RunParallel(func(pb *testing.PB) {\r\n\t\tfor pb.Next() {\r\n\t\t\tfib(30)\r\n\t\t}\r\n\t})\r\n}\r\n```\r\n执行结果\r\n```shell\r\ngoos: windows\r\ngoarch: amd64\r\npkg: go_test\r\ncpu: AMD Ryzen 5 3600 6-Core Processor\r\nBenchmarkParallelFib\r\nBenchmarkParallelFib-12 1663 621800 ns/op\r\nPASS\r\n```\r\n\r\n## 7.1. b.RunParallel\r\n这是 `*testing.B` 对象的一个方法,其作用是并行执行基准测试。它会启动多个goroutine来并行运行传入的函数。\r\n- `b.RunParallel` 方法会尽力让基准测试以`GOMAXPROCS(CPU 核心数)`个并发来执行\r\n- 可以通过`-cpu`命令来指定并发度\r\n- 可以通过 `b.SetParallelism(p)` 来显式设置并发度。这会将并发 goroutine 的数量设置为 `p * GOMAXPROCS`\r\n## 7.2. `func(pb *testing.PB){}`\r\n这是一个匿名函数,会被并行执行。`pb *testing.PB`:`pb` 是`*testing.PB` 类型的指针,它是 `b.RunParallel` 方法提供的一个对象,用于控制并行基准测试的迭代。`pb.Next()`:这是 `*testing.PB` 对象的一个方法,它会返回一个布尔值。只要返回 `true`,就表明基准测试应该继续迭代。\r\n- **循环控制**:它类似于普通基准测试中的 `i < b.N` 条件,返回一个布尔值表示是否继续执行下一次迭代。\r\n- **计数机制**:Go 测试框架会确保所有并发goroutine总共执行的操作次数接近`b.N`。\r\n- **动态分配**:与单线程测试不同,`b.N`的操作次数会被动态分配给多个goroutine,而 `pb.Next()`负责协调这一分配过程。\r\n- **负载均衡**:测试框架会自动平衡各个goroutine之间的工作负载,避免某些 goroutine工作过多或过少。\r\n> [!note]\r\n> 在并发基准测试中也可以使用 `-benchtime` 标志来指定测试运行的时间或者次数。这个标志对并发测试和普通基准测试都有效。"},{"id":"go性能分析","title":"Go性能分析","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"go性能分析","description":"golang中性能调试优化的方法包括: - benchmark:基准测试,对特定代码的运行时间和内存信息等进行测试 - profiling: 程序分析,程序的运行画像,在程序执行期间,通过采样收集的数据对程序进行分析 - Trace:跟踪,在程序执行期间,通过采集发生的事件数据对程序进行分析 > [...","relativePath":"Tech/Code/go/程序调优/go性能分析.md","rawContent":"golang中性能调试优化的方法包括:\r\n- benchmark:**基准测试**,对特定代码的运行时间和内存信息等进行测试\r\n- profiling: **程序分析**,程序的运行画像,在程序执行期间,通过采样收集的数据对程序进行分析\r\n- Trace:**跟踪**,在程序执行期间,通过采集发生的事件数据对程序进行分析\r\n> [!question]\r\n>profiling 和 trace 有啥区别? \r\n>Answer: profiling 分析没有时间线,trace 分析有时间线。\r\n\r\ngo中的性能分析是通过`分析器`(profiler)工具进行检测来实现的,称为pprof。pprof工具可以用来监测进程的运行数据,用于监控程序的性能及状况,包括以下指标。\r\n\r\n> CPU— 确定应用程序的时间花在了哪里 \r\n> Goroutine— 报告正在运行的 goroutines 堆栈跟踪 \r\n> Heap— 报告堆内存分配以监视当前内存使用情况并检查可能的内存泄漏 \r\n> Mutex— 报告锁争情况来分析代码中互斥锁使用行为以及应用程序是否在锁定调用上花费了太多时间 \r\n> Block— 显示 goroutines 阻塞等待同步原语的位置"},{"id":"pprof","title":"Pprof","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":7,"slug":"pprof","description":"1. 使用方式 必须在代码里引入才能使用,不像Java里jdk工具包中的 、、、、 工具可以单独使用。可以从以下两个包中引入 Golang pprof的使用方式主要有两种 1. 在程序中通过http接口的方式暴露相应的pprof的采集控制界面,需要依赖, 使用 包来进行封装。 2. 可以用来产生du...","relativePath":"Tech/Code/go/程序调优/pprof.md","rawContent":"\r\n# 1. 使用方式\r\n`pprof`必须在代码里引入才能使用,不像Java里jdk工具包中的 `jps`、`jstat`、`jinfo`、`jstack`、`jmap` 工具可以单独使用。`pprof`可以从以下两个包中引入\r\n```go\r\nimport \"net/http/pprof\"\r\nimport \"github.com/pkg/profile\"\r\n```\r\nGolang pprof的使用方式主要有两种\r\n1. 在程序中通过http接口的方式暴露相应的pprof的采集控制界面,需要依赖`net/http/pprof`,`net/http/pprof` 使用 `runtime/pprof` 包来进行封装。\r\n2. `github.com/pkg/profile` 可以用来产生dump文件,程序中通过代码开启对应指标的采集样本功能,采集一段时间的样本后生成二进制文件,最后通过 `go tool pprof` 命令去对样本的数据进行分析。\r\n## 1.1. http接口的方式暴露采集控制界面\r\n```go\r\nimport (\r\n\t\"net/http\"\r\n\t// 代码里引入 pprof\r\n\t_ \"net/http/pprof\"\r\n)\r\n\r\nfunc main() {\r\n // 代码里引入http server \r\n\tgo func() {\r\n\t\tif err := http.ListenAndServe(\":6060\", nil); err != nil {\r\n\t\t\tlog.Fatal(err)\r\n\t\t}\r\n\t\tos.Exit(0)\r\n\t}()\r\n\r\n // 省略业务代码\r\n}\r\n```\r\n可以通过 `http://127.0.0.1:6060\r\n/debug/pprof/`来访问 pprof\r\n## 1.2. 通过代码开启\r\n```go\r\nimport (\r\n \"github.com/pkg/profile\"\r\n \"time\"\r\n)\r\n\r\nfunc main() {\r\n // 同时启用CPU和内存分析\r\n p := profile.Start(\r\n profile.ProfilePath(\"./profiles\"), // 指定输出目录\r\n profile.CPUProfile, // 启用CPU分析\r\n //profile.MemProfile, // 启用内存分析\r\n //profile.MemProfileRate(4096), // 设置内存分析的采样率\r\n //profile.BlockProfile, // 启用阻塞分析\r\n //profile.MutexProfile, // 启用互斥锁分析\r\n profile.NoShutdownHook, // 禁用自动关闭钩子\r\n )\r\n \r\n // 您的程序代码...\r\n \r\n // 在适当的时机停止分析\r\n defer p.Stop()\r\n}\r\n```\r\n使用标准的pkg/profile包时,一次只能激活一种主要的分析类型(CPU或内存或阻塞等)。如果需要同时收集多种类型,需要直接使用底层的runtime/pprof包。t\r\n`profile.Start`函数签名如下\r\n```go\r\nfunc Start(options ...func(*Profile)) interface {\r\n\tStop()\r\n}\r\n```\r\n- 函数名Start\r\n- 函数参数为可变长数组,每个元素是一个函数`func(*Profile)`,入参是`*Profile` 指针,返回值为void\r\n- 函数的返回值为内联定义的接口类型,这个接口只有一个方法要求: `Stop()`,无入参且不返回值\r\n > [!note]\r\n > 可以将Start和Stop方法封装成函数,通过api或信号量的触发的方式调用,实现程序运行的任意时刻触发dump分析\r\n# 2. 监控指标\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250408223126.png)\r\n## 2.1. allocs\r\n作用:内存分配分析,包含哪些函数分配了内存以及分配了多少内存。\r\n使用场景:优化内存分配,分析内存泄露\r\n## 2.2. blcok\r\n作用:显示导致goroutingz阻塞的操作,如互斥锁、通道操作等\r\n使用场景:发现并解决程序中的并发瓶颈,特别是因锁竞争导致的性能问题\r\n## 2.3. cmdline\r\n作用:显示启动程序时使用的命令行参数\r\n使用场景:确认程序是以什么参数启动的,对调试特定配置下的问题很有用\r\n## 2.4. gorouting\r\n作用:显示当前所有gorouting的堆栈\r\n使用场景:查看程序中的所有gorouting的状态,诊断死锁、泄露或其他并发问题\r\n## 2.5. heap\r\n作用:显示当前堆内存的使用情况,包括哪些对象占用内存及大小\r\n使用场景:查找内存泄露,分析程序中内存占用过高的对象\r\n## 2.6. mutex\r\n作用:显示互斥锁的竞争情况,包括哪些锁被长时间持有或频繁竞争\r\n使用场景:发现并解决因互斥锁导致的性能瓶颈\r\n## 2.7. profile\r\n作用:记录cpu使用情况,显示哪些函数占用了最多的cpu时间\r\n使用场景:识别程序中的cpu热点,优化计算密集型函数\r\n\r\n当它被激活时,应用程序默认通过SIGPROF信号要求操作系统每10毫秒中断一次。当应用程序收到 SIGPROF 时,它会暂停当前活动并将执行转移到分析器。分析器收集诸如当前 goroutine活动之类的数据,并汇总可以检索的执行统计信息;然后停止分析并继续执行直到下一次的 SIGPROF。默认情况下,访问`/debug/pprof/profile`地址会执行30秒的 CPU分析。在 30 秒内,我们的应用程序每10毫秒中断一次。使用seconds参数将分析应该持续多长时间传递给路由(例如 /debug/pprof/profile?seconds=15),也可以更改中断率(甚至小于10毫秒)。但多数情况下,10 毫秒应该足够了,再减小这个值(意味着增加频率)时,可能会对性能产生较大影响。30 秒后,就可以下载 CPU 分析器的结果。\r\n\r\n> [!note]\r\n> 在运行benchmark的时候,可以通过命令行增加-cpuprofile参数的方式激活\r\n### 2.7.1. 结果分析\r\n可以通过`go tool pprof cpuprofile/cpu.pprof`生成一个交互终端来进行分析。\r\n```shell\r\n(base) ➜ cpu go tool pprof cpuprofile/cpu.pprof\r\nType: cpu\r\nTime: Mar 4, 2024 at 3:14pm (CST)\r\nDuration: 4.35s, Total samples = 200ms ( 4.60%)\r\nEntering interactive mode (type \"help\" for commands, \"o\" for options)\r\n(pprof) top\r\nShowing nodes accounting for 200ms, 100% of 200ms total\r\nShowing top 10 nodes out of 16\r\n flat flat% sum% cum cum%\r\n 190ms 95.00% 95.00% 190ms 95.00% syscall.syscall\r\n 10ms 5.00% 100% 10ms 5.00% runtime.pthread_cond_signal\r\n 0 0% 100% 190ms 95.00% internal/poll.(*FD).Write\r\n 0 0% 100% 190ms 95.00% internal/poll.ignoringEINTRIO (inline)\r\n 0 0% 100% 190ms 95.00% log.(*Logger).output\r\n 0 0% 100% 190ms 95.00% log.Println (inline)\r\n 0 0% 100% 190ms 95.00% main.busyCpu\r\n 0 0% 100% 190ms 95.00% os.(*File).Write\r\n 0 0% 100% 190ms 95.00% os.(*File).write (inline)\r\n 0 0% 100% 10ms 5.00% runtime.exitsyscallfast.func1。\r\n```\r\n每个列的含义如下:\r\n`flat`:函数自身的运行耗时(排除了子函数的调用耗时)\r\n`flat%`:flat运行耗时占用总的采集样本的时间和的比例,这里所有节点运行的flat时间和为200ms。\r\n`sum%`:函数自身和其之上的函数运行的flat时间占所有采集样本时间总和的比例。\r\n`cum`:当前函数和其子函数的调用总的运行时间\r\n`cum%`:cum耗时占总的采集样本的时间和的比例。\r\n\r\n也可以通过`go tool pprof -http=:8082 cpuprofile/cpu.pprof `的方式启动一个webserver进行分析。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250408224203.png)\r\n\r\n当然,也可以选择火焰图的方式进行展示并分析\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250408224414.png)\r\n在web界面还可以通过source视图去查看函数节点的耗时以及它的子调用函数中耗时的地方,第一栏时间是flat耗时,第二栏时间是cum耗时。 耗时百分比是cum耗时占样本总和的百分比。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250408224356.png)\r\n## 2.8. threadtrace\r\n作用:显示导致创建操作系统线程的堆栈跟踪\r\n使用场景:分析程序中操作系统线程的创建情况,优化系统资源使用\r\n## 2.9. trace\r\n作用:记录程序执行过程中的详细事件序列\r\n使用场景:全面分析程序行为,包括gorouting的创建、调度和阻塞等细节问题\r\n\r\n"},{"id":"程序调优","title":"程序调优","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"程序调优","description":"","relativePath":"Tech/Code/go/程序调优/程序调优.md","rawContent":""},{"id":"进程、线程与协程","title":"进程、线程与协程","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":7,"slug":"进程线程与协程","description":"1. go中线程的数量 Go 使用Goroutine 调度器 (Scheduler) 来管理Goroutine的执行。调度器的核心概念如下 1.1. GMP模型 goalng采用特有的GMP模型。 1. G(Goroutine):指的是 Go 代码中的 Goroutine。 2. M(machine...","relativePath":"Tech/Code/go/进程、线程与协程.md","rawContent":"# 1. go中线程的数量\r\nGo 使用**Goroutine 调度器 (Scheduler)** 来管理Goroutine的执行。调度器的核心概念如下\r\n## 1.1. GMP模型\r\ngoalng采用特有的**GMP**模型。\r\n1. G(Goroutine):指的是 Go 代码中的 Goroutine。\r\n2. M(machine):它直接关联一个os内核线程,用于执行G。\r\n3. P(Processor):P里面一般会存当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503021729567.png)\r\nP与M一般是一一对应的。P(上下文)管理着一组G(goroutine)挂载在M(内核线程)上运行,图中左边蓝色为正在执行状态的goroutine,右边为待执行状态的goroutiine队列。GO不能设置使用的线程数上限,但可以通过设置环境变量`GOMAXPROCS`的值或程序运行`runtime.GOMAXPROCS()`设置go进程使用的逻辑CPU核心数,间接影响线程的使用数量。\r\n```go\r\npackage main\r\nimport (\r\n \"fmt\"\r\n \"runtime\"\r\n)\r\n\r\nfunc main() {\r\n runtime.GOMAXPROCS(2) // 设定最多使用2个CPU核心\r\n fmt.Println(\"GOMAXPROCS:\", runtime.GOMAXPROCS(0)) // 获取当前值\r\n}\r\n```\r\n\r\n## 1.2. 默认GOMAXPROCS\r\n> [!note]\r\n> - 在 Go 1.5 之前,`GOMAXPROCS` 默认是 `1`(单线程执行)。\r\n> - **Go 1.5 之后**,Go 运行时默认将 `GOMAXPROCS` 设为 **CPU 逻辑核心数**(`runtime.NumCPU()`)。这样可以**充分利用 CPU 并行计算能力**,提高 Goroutine 执行效率\r\n\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"runtime\"\r\n)\r\n\r\nfunc main() {\r\n\tfmt.Println(\"默认 GOMAXPROCS:\", runtime.GOMAXPROCS(0)) // 传入 0 只获取当前值,不修改\r\n}\r\n```\r\n输出:默认 GOMAXPROCS: 32,查看系统CPU数量\r\n```shell\r\ndevbox@devbox:~/project$ lscpu \r\nArchitecture: x86_64\r\n CPU op-mode(s): 32-bit, 64-bit\r\n Address sizes: 40 bits physical, 48 bits virtual\r\n Byte Order: Little Endian\r\nCPU(s): 32\r\n On-line CPU(s) list: 0-31\r\nVendor ID: GenuineIntel\r\n```\r\n## 1.3. GMP调度\r\n当一个os线程在执行M1一个G1发生阻塞时,调度器让M1抛弃P,等待G1返回,然后另起一个M2接收P来执行剩下的goroutine队列(G2、G3...),这是golang调度器厉害的地方,可以保证有足够的线程来运行剩下所有的goroutine。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503021730188.png)\r\n当G1结束后,M1会重新拿回P来完成,如果拿不到就丢到全局runqueue中,然后自己放到线程池或转入休眠状态。空闲的上下文P会周期性的检查全局runqueue上的goroutine,并且执行它。另一种情况就是当有些P1太闲而其他P2很忙碌的时候,会从其他上下文P2拿一些G来执行。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503021731057.png)\r\n\r\n# 2. 进程、线程、协程\r\n**进程**:系统中所有的应用程序都是以进程(process)的方式运行,是系统进行资源分配和调度的基本单位,每个进程都有自己的独立的地址空间,使得进程之间的地址空间相互隔离。\r\n**线程**:线程是CPU调度的最小单元,通常意义上,一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间(包括代码段、数据段、堆等)及一些进程级的资源(如打开的文件和信号)。\r\n**协程**:协程在Go语言中,由轻量级线程实现,由Go运行时(runtime)管理。\r\n\r\n> [!note]\r\n> 并发:多线程程序在单核上运行\r\n> 并行:多线程程序在多核上运行\r\n# 3. 协程与进程、线程的区别\r\n1)进程拥有自己的堆栈,不共享堆和栈,是由操作系统进行调度的。\r\n2)线程拥有自己的独立的栈和共享的堆,也是由操作系统进行调度。\r\n3)协程共享堆,不共享栈,协程的调度由用户控制。\r\n## 3.1. 协程的优点\r\n1. **代码开发简单**,可以将异步处理逻辑代码用同步的方式编写,将多个异步操作集中到一个函数中完成。\r\n2. 单线程模式,**没有线程安全的问题**,不需要加锁操作。\r\n3. **性能好**,协程是用户态线程,切换更加高效。\r\n4. **Go协程占用内存小**。执行Go协程只需要极少的栈内存(大概4~5KB),默认情况下,线程栈的大小为1MB。Goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈,所以它非常廉价,我们可以很轻松的创建上万个Goroutine,但它们并不是被操作系统所调度执行。\r\n\r\n## 3.2. Go协程调用跟切换比线程效率高\r\n线程并发执行流程:\r\n线程是内核对外提供的服务,应用程序可以通过系统调用让内核启动线程,由内核来负责线程调度和切换,线程在等待IO操作时标为unrunnable状态会触发上下文切换。现代操作系统一般采用抢占式调度,上下文切换一般发生在时钟中断和系统调用返回前,调度器计算当前线程的时间片,如果需要切换就从运行队列中选出一个目标线程,保存当前线程的环境,并且恢复目标线程的运行环境,最典型的就是切换ESP指向目标线程内核堆栈,将EIP指向目标线程上次被调度出时的指令地址。一旦我们创建完线程,就无法决定它什么时候获得时间片,什么时候让出时间片,这里都交给了内核。但我们编写协程时可以控制,可控的切换时机和很小的切换代价,从操作系统有没有调度权来看,**协程就是因为不需要进行内核态的切换**。\r\n\r\nGo协程并发执行流程:\r\n不依赖操作系统和其提供的线程,Golang自己实现的CSP并发模型实现:M,P,G\r\nGo协程也叫用户态线程,协程之间的切换发生在用户态,在用户态没有时钟中断,系统调用等机制,因此效率高。"},{"id":"go面向对象","title":"Go面向对象","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"go面向对象","description":"1. 自定义数据类型 增强代码可读性 2. 方法接收器 只有自定义类型(或内置类型)才能够绑定方法,从而获得面向对象的特性。例如,为 绑定 方法。,使得User对象实现了Name方法。 在Go语言中,接口的实现是隐式的,也就是说只要一个自定义类型实现了接口中声明的所有方法,那么它就被认为实现了该接口...","relativePath":"Tech/Code/go/高级语法/go面向对象.md","rawContent":"# 1. 自定义数据类型\r\n增强代码可读性\r\n```go\r\ntype city string \r\n \r\nfunc main() { \r\n beijing := city(\"北京\") \r\n shanghai := city(\"上海\") \r\n fmt.Println(beijing) // 北京 \r\n fmt.Println(shanghai) // 上海\r\n}8\r\n```\r\n# 2. 方法接收器\r\n只有自定义类型(或内置类型)才能够绑定方法,从而获得面向对象的特性。例如,为`type User struct {...}` 绑定 `func (u User) Name() string {...}` 方法。,使得User对象实现了Name方法。\r\n```go\r\ntype User struct { \r\n name string \r\n age int32 \r\n} \r\n \r\nfunc (u User) Name() string { \r\n return u.name \r\n} \r\n \r\nfunc main() { \r\n userA := User{ \r\n name: \"jack\", \r\n age: 10, \r\n } \r\n \r\n fmt.Println(userA.Name()) // jack \r\n}\r\n```\r\n在Go语言中,接口的实现是隐式的,也就是说只要一个自定义类型实现了接口中声明的所有方法,那么它就被认为实现了该接口,无需显式声明。这种隐式实现的方式,让接口的使用变得非常灵活。\r\n```go\r\n// Shape 接口是一种抽象类型,它定义了一组方法签名,但不包含实现代码 \r\ntype Shape interface { \r\n Area() float64 \r\n Perimeter() float64 \r\n} \r\n \r\n// Rectangle 实现了接口中声明的所有方法,那么它就被认为实现了该接口 \r\ntype Rectangle struct { \r\n Width, Height float64 \r\n} \r\n \r\nfunc (r Rectangle) Area() float64 { \r\n return r.Width * r.Height \r\n} \r\n \r\nfunc (r Rectangle) Perimeter() float64 { \r\n return 2 * (r.Width + r.Height) \r\n} \r\n\r\ntype Square struct { \r\n edgeSize float64 \r\n} \r\n \r\nfunc (r Square) Area() float64 { \r\n return r.edgeSize * r.edgeSize \r\n}\r\n \r\nfunc printInfo(shape Shape) { \r\n fmt.Println(shape.Area()) // 200 \r\n fmt.Println(shape.Perimeter()) // 60 \r\n} \r\n \r\nfunc main() { \r\n myRectangle := Rectangle{ \r\n Width: 10.0, \r\n Height: 20.0, \r\n } \r\n printInfo(myRectangle) \r\n \r\n\tmySquare := Square{ \r\n\t edgeSize: 10, \r\n\t} \r\n\t// Cannot use 'mySquare' (type Square) as the type Shape Type does not implement 'Shape' \r\n\tprintInfo(mySquare)\r\n}\r\n```\r\n"},{"id":"反射","title":"反射","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":15,"slug":"反射","description":"1. 任意类型 在 Go 语言中, 常被称为“空接口”,它的确能承载fff88f\">任意类型的值,但这种“万能”特性背后存在明显的权衡和局限性。也可以用关键词替代, go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息 对于这个例子, 的类型信息是 ,数据信息是 ,这两部分信息都是存储...","relativePath":"Tech/Code/go/高级语法/反射.md","rawContent":"# 1. 任意类型\r\n在 Go 语言中,`interface{}` 常被称为“空接口”,它的确能承载任意类型的值,但这种“万能”特性背后存在明显的**权衡和局限性**。`interface{}`也可以用`any`关键词替代,`type any = interface{}`\r\n\r\n```go\r\npackage main\r\n\r\nimport \"log\"\r\n\r\nfunc main() {\r\n\tvar anyItem interface{}\r\n\t// 等价于var anyItem any \r\n\tanyItem = 42 // int\r\n\tanyItem = \"hello\" // string\r\n\tanyItem = struct{}{} // 初始化空结构体\r\n\tlog.Printf(\"%v\", anyItem)\r\n}\r\n```\r\ngo 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息\r\n```go\r\nvar a = 1 \r\nvar b interface{} = a\r\n```\r\n对于这个例子,`b` 的类型信息是 `int`,数据信息是 `1`,这两部分信息都是存储在 `b` 里面的。`b` 的内存结构\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302192731548.png)\r\n在上图中,`b` 的类型实际上是 `eface`,它是一个空接口,它的定义如下:\r\n```go\r\ntype eface struct {\r\n _type *_type\r\n data unsafe.Pointer\r\n}\r\n```\r\n也就是说,一个`interface{}`中实际上既包含了变量的类型信息,也包含了类型的数据。正因为如此,我们才可以通过反射来获取到变量的类型信息,以及变量的数据信息。\r\n# 2. 反射\r\nGo中的反射是用reflect包实现,reflect包实现了运行时的反射能力,能够让程序操作不同的对象。\r\n\r\nGo中的反射是建立在类型系统之上,它与空接口`interface{}`密切相关。每个`interface{}`类型的变量包含一对值`(type,value)`,type 表示变量的类型信息,value 表示变量的值信息。\r\n> [!info]\r\n> 所以 nil!=nil,就可以理解了。\r\n\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"log\"\r\n)\r\n\r\nfunc main() {\r\n\tvar a any\r\n\tvar b any\r\n\tlog.Println(a, b) // \r\n\tlog.Println(a != b) // false\r\n}\r\n\r\n```\r\n\r\nreflect有两个核心方法:\r\n- `reflect.TypeOf()` 获取类型信息,返回 [Type](https://cs.opensource.google/go/go/+/refs/tags/go1.18.8:src/reflect/type.go;l=39) 类型;\r\n- `reflect.ValueOf()` 获取数据信息,返回 [Value](https://cs.opensource.google/go/go/+/refs/tags/go1.18.8:src/reflect/value.go;l=39) 类型。\r\n阅读`TypeOf` 和 `ValueOf` 的源码会发现,这两个方法都接收一个 `interface{}` 类型的参数,然后返回一个 `reflect.Type` 和 `reflect.Value` 类型的值。这也就是为什么我们可以通过 `reflect.TypeOf` 和 `reflect.ValueOf` 来获取到一个变量的类型和值的原因。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302193030734.png)\r\n## 2.1. 反射定律\r\n1. 反射可以将 `interface` 类型变量转换成反射对象。\r\n2. 反射可以将反射对象还原成 `interface` 对象。\r\n3. 如果要修改反射对象,那么反射对象必须是可设置的(`CanSet`)。\r\n### 2.1.1. 反射可以将 `interface`类型变量转换成反射对象\r\n```go\r\nvar a = 1\r\ntypeOfA := reflect.TypeOf(a)\r\nvalueOfA := reflect.ValueOf(a)\r\n```\r\n### 2.1.2. 反射可以将反射对象还原成 `interface` 对象\r\n我们可以通过 `reflect.Value.Interface` 来获取到反射对象的 `interface` 对象,也就是传递给 `reflect.ValueOf` 的那个变量本身。 不过返回值类型是 `interface{}`,所以我们需要进行类型断言。\r\n```go\r\nvar a = 1\r\nvalueOfA := reflect.ValueOf(a)\r\ni := valueOfA.Interface()\r\nfmt.Println(i.(int)) // 1\r\n```\r\n### 2.1.3. 如果要修改反射对象,那么反射对象必须是可设置的(`CanSet`)\r\n我们可以通过 `reflect.Value.CanSet` 来判断一个反射对象是否是可设置的。如果是可设置的,我们就可以通过 `reflect.Value.Set` 来修改反射对象的值。 这其实也是非常场景的使用反射的一个场景,通过反射来修改变量的值。\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"reflect\"\r\n)\r\n\r\nfunc main() {\r\n\tvar x float64 = 3.4\r\n\tv := reflect.ValueOf(&x)\r\n\tfmt.Println(\"settability of v:\", v.CanSet()) // false\r\n\tfmt.Println(\"settability of v:\", v.Elem().CanSet()) // true\r\n}\r\n```\r\n反射对象是可设置的条件:\r\n- 反射对象是一个指针\r\n- 这个指针指向的是一个可设置的变量\r\n在我们传递一个值给 `reflect.ValueOf` 的时候,如果这个值只是一个普通的变量,那么 `reflect.ValueOf`会返回一个不可设置的反射对象。 因为这个值实际上被拷贝了一份,我们如果通过反射修改这个值,那么实际上是修改的这个拷贝的值,而不是原来的值。 所以go语言在这里做了一个限制,如果我们传递进 `reflect.ValueOf` 的变量是一个普通的变量,那么在我们设置反射对象的值的时候,会报错。 所以在上面这个例子中,我们传递了`x`的指针变量作为参数。这样,运行时就可以找到 `x` 本身,而不是 `x` 的拷贝,所以就可以修改 `x` 的值了。\r\n\r\n但同时我们也注意到了,在上面这个例子中,`v.CanSet()` 返回的是 `false`,而 `v.Elem().CanSet()` 返回的是 `true`。 这是因为,`v` 是一个指针,而 `v.Elem()` 是指针指向的值,对于这个指针本身,我们修改它是没有意义的,我们可以设想一下, 如果我们修改了指针变量(也就是修改了指针变量指向的地址),那会发生什么呢?那样我们的指针变量就不是指向 `x` 了, 而是指向了其他的变量,这样就不符合我们的预期了。所以 `v.CanSet()` 返回的是 `false`。而 `v.Elem().CanSet()` 返回的是 `true`。这是因为 `v.Elem()` 才是 `x` 本身,通过 `v.Elem()` 修改 `x` 的值是没有问题的。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302194050775.png)\r\n## 2.2. elem方法\r\n`reflect.Value` 和 `reflect.Type` 这两个反射对象都有 `Elem` 方法,既然是不同的对象,那么它们的作用自然是不一样的。\r\n### 2.2.1. reflect.Value 的 Elem 方法\r\n`reflect.Value` 的 `Elem` 方法的作用是**获取指针指向的值,或者获取接口的动态值**。也就是说,能调用 `Elem` 方法的反射对象,必须是一个指针或者一个接口。 在使用其他类型的 `reflect.Value` 来调用 `Elem` 方法的时候,会 `panic`:\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"log\"\r\n\t\"reflect\"\r\n)\r\n\r\nfunc main() {\r\n\tvar a = 1\r\n\r\n\tvar b = &a\r\n\tvar v reflect.Value = reflect.ValueOf(b)\r\n\tlog.Printf(\"%v\", v.Elem()) // 1\r\n\tlog.Println(\"value is: \", v) // 10xc0000120d0\r\n\r\n\t// panic: reflect: call of reflect.Value.Elem on int Value\r\n\treflect.ValueOf(a).Elem()\r\n}\r\n```\r\n对于指针很好理解,其实作用类似解引用。而对于接口,还是要回到 `interface` 的结构本身,因为接口里包含了类型和数据本身,所以 `Elem` 方法就是获取接口的数据部分(也就是 `iface` 或 `eface` 中的 `data` 字段)。\r\n\r\n指针类型:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302200706997.png)\r\n接口类型:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302200814348.png)\r\n### 2.2.2. reflect.Type的Elem方法\r\n`reflect.Type` 的 `Elem` 方法的作用是**获取数组、chan、map、指针、切片关联元素的类型信息**,也就是说,对于 `reflect.Type` 来说, 能调用 `Elem` 方法的反射对象,必须是**数组、chan、map、指针、切片中的一种**,其他类型的 `reflect.Type` 调用 `Elem` 方法会 `panic`。\r\n```go\r\nt1 := reflect.TypeOf([3]int{1, 2, 3}) // 数组 [3]int\r\nfmt.Println(t1.String()) // [3]int\r\nfmt.Println(t1.Elem().String()) // int\r\n```\r\n需要注意的是,如果我们要获取 map 类型 key 的类型信息,需要使用 `Key` 方法,而不是 `Elem` 方法。\r\n```go\r\nm := make(map[string]string)\r\nt1 := reflect.TypeOf(m)\r\nfmt.Println(t1.Key().String()) // string\r\n```\r\n### 2.2.3. interface方法\r\n这也是非常常用的一个方法,`reflect.Value` 的 `Interface` 方法的作用是**获取反射对象的动态值**。 也就是说,如果反射对象是一个指针,那么 `Interface` 方法会返回指针指向的值。\r\n\r\n简单来说,如果 `var i interface{} = x`,那么 `reflect.ValueOf(x).Interface()` 就是 `i` 本身,只不过其类型是 `interface{}` 类型。可以通过`reflect.ValueOf(x).Interface().(T)`来断言获取实际类型的值。\r\n## 2.3. kind方法\r\n在go中,我们可以使用type关键词基于基本类型来定义各种新的类型,如:\r\n```go\r\n// Kind 是 int\r\ntype myIny int\r\n// Kind 是 Struct\r\ntype Person struct {\r\n Name string\r\n Age int\r\n}\r\n```\r\n不管我们定义了多少种类型,在go看来都是下面的基本类型中的一个:\r\n```go\r\ntype Kind uint\r\n\r\nconst (\r\n Invalid Kind = iota\r\n Bool\r\n Int\r\n Int8\r\n Int16\r\n Int32\r\n Int64\r\n Uint\r\n Uint8\r\n Uint16\r\n Uint32\r\n Uint64\r\n Uintptr\r\n Float32\r\n Float64\r\n Complex64\r\n Complex128\r\n Array\r\n Chan\r\n Func\r\n Interface\r\n Map\r\n Pointer\r\n Slice\r\n String\r\n Struct\r\n UnsafePointer\r\n)\r\n```\r\n我们定义的类型在go的类型系统中都是基本类型的一种,这个基本类型就是 `Kind`。 也正因为如此,我们可以通过**有限的** `reflect.Type` 的 `Kind` 来进行类型判断。 也就是说,我们在通过反射来判断变量的类型的时候,只需要枚举 `Kind` 中的类型,然后通过 `reflect.Type` 的 `Kind` 方法来判断即可。\r\n```go\r\nfunc display(path string, v reflect.Value) {\r\n switch v.Kind() {\r\n case reflect.Invalid:\r\n fmt.Printf(\"%s = invalid\\n\", path)\r\n case reflect.Slice, reflect.Array:\r\n for i := 0; i < v.Len(); i++ {\r\n display(fmt.Sprintf(\"%s[%d]\", path, i), v.Index(i))\r\n }\r\n case reflect.Struct:\r\n for i := 0; i < v.NumField(); i++ {\r\n fieldPath := fmt.Sprintf(\"%s.%s\", path, v.Type().Field(i).Name)\r\n display(fieldPath, v.Field(i))\r\n }\r\n case reflect.Map:\r\n for _, key := range v.MapKeys() {\r\n display(fmt.Sprintf(\"%s[%s]\", path, formatAny(key)), v.MapIndex(key))\r\n }\r\n case reflect.Pointer:\r\n if v.IsNil() {\r\n fmt.Printf(\"%s = nil\\n\", path)\r\n } else {\r\n display(fmt.Sprintf(\"(*%s)\", path), v.Elem())\r\n }\r\n case reflect.Interface:\r\n if v.IsNil() {\r\n fmt.Printf(\"%s = nil\\n\", path)\r\n } else {\r\n fmt.Printf(\"%s.type = %s\\n\", path, v.Elem().Type())\r\n display(path+\".value\", v.Elem())\r\n }\r\n default:\r\n fmt.Printf(\"%s = %s\\n\", path, formatAny(v))\r\n }\r\n}\r\n```\r\n\r\n## 2.4. addressable\r\ngo反射中最后一个很重要的话题是addressable。在go的反射系统中有两个关于寻址的方法:`CanAddr`和`CanSet`。\r\n`CanAddr`方法的作用是判断反射对象是否可以寻址\r\n- 如果`CanAddr`返回 true,那么我们就可以通过 Addr 方法来获取反射对象的地址。\r\n- 如果`CanAddr`返回 false,那么我们就不能通过 Addr 方法来获取反射对象的地址。对于这种情况,我们就无法通过反射对象来修改变量的值。\r\n但是,CanAddr是true并不是说`reflect.Value`一定就能修改变量的值了。`reflect.Value`还有一个方法`CanSet`,只有`CanSet`返回 true,我们才能通过反射对象来修改变量的值。\r\n## 2.5. 获取类型信息 - reflect.Type\r\n`reflect.Type` 是一个接口,它代表了一个类型。我们可以通过 `reflect.TypeOf` 来获取一个类型的 `reflect.Type` 对象。 我们使用 `reflect.Type` 的目的通常是为了获取类型的信息,比如类型是什么、类型的名称、类型的字段、类型的方法等等。 又或者最常见的场景:结构体中的 `json` 的 `tag`,它是没有语义的,它的作用就是为了在序列化的时候,生成我们想要的字段名。 而这个 `tag` 就是需要通过反射来获取的。\r\n\r\n`reflect.Type` 这个接口有很多方法,下面这些方法是**所有的类型通用**的方法:\r\n```go\r\n// Type 是 Go 类型的表示。\r\n//\r\n// 并非所有方法都适用于所有类型。\r\n// 在调用 kind 具体方法之前,先使用 Kind 方法找出类型的种类。因为调用一个方法如果类型不匹配会导致 panic\r\n//\r\n// Type 类型值是可以比较的,比如用 == 操作符。所以它可以用做 map 的 key\r\n// 如果两个 Type 值代表相同的类型,那么它们一定是相等的。\r\ntype Type interface {\r\n // Align 返回该类型在内存中分配时,以字节数为单位的字节数\r\n Align() int\r\n \r\n // FieldAlign 返回该类型在结构中作为字段使用时,以字节数为单位的字节数\r\n FieldAlign() int\r\n \r\n // Method这个方法返回类型方法集中的第i个方法。\r\n // 如果 i 不在[0, NumMethod()]范围内,就会 panic。\r\n // 对于非接口类型 T 或 *T,返回的 Method 的 Type 和 Func 字段描述了一个函数,\r\n // 其第一个参数是接收者,并且只能访问导出的方法。\r\n // 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。\r\n // 方法是按字典序顺序排列的。\r\n Method(int) Method\r\n\r\n // MethodByName 返回类型的方法集中具有该名称的方法和一个指示是否找到该方法的布尔值。\r\n // 对于非接口类型 T 或 *T,返回的 Method 的 Type 和 Func 字段描述了一个函数,\r\n // 其第一个参数是接收者。\r\n // 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。\r\n MethodByName(string) (Method, bool)\r\n\r\n // NumMethod 返回使用 Method 可以访问的方法数量。\r\n // 对于非接口类型,它返回导出方法的数量。\r\n // 对于接口类型,它返回导出和未导出方法的数量。\r\n NumMethod() int\r\n\r\n // Name 返回定义类型在其包中的类型名称。\r\n // 对于其他(未定义的)类型,它返回空字符串。\r\n Name() string\r\n\r\n // PkgPath 返回一个定义类型的包的路径,也就是导入路径,导入路径是唯一标识包的类型,如 \"encoding/base64\"。\r\n // 如果类型是预先声明的(string, error)或者没有定义(*T, struct{}, []int,或 A,其中 A 是一个非定义类型的别名),包的路径将是空字符串。\r\n PkgPath() string\r\n\r\n // Size 返回存储给定类型的值所需的字节数。它类似于 unsafe.Sizeof.\r\n Size() uintptr\r\n\r\n // String 返回该类型的字符串表示。\r\n // 字符串表示法可以使用缩短的包名。\r\n // (例如,使用 base64 而不是 \"encoding/base64\")并且它并不能保证类型之间是唯一的。如果是为了测试类型标识,应该直接比较类型 Type。\r\n String() string\r\n\r\n // Kind 返回该类型的具体种类。\r\n Kind() Kind\r\n\r\n // Implements 表示该类型是否实现了接口类型 u。\r\n Implements(u Type) bool\r\n\r\n // AssignableTo 表示该类型的值是否可以分配给类型 u。\r\n AssignableTo(u Type) bool\r\n\r\n // ConvertibleTo 表示该类型的值是否可转换为 u 类型。\r\n ConvertibleTo(u Type) bool\r\n\r\n // Comparable 表示该类型的值是否具有可比性。\r\n Comparable() bool\r\n}\r\n```\r\n下面是**某些类型特定**的方法,对于这些方法,如果我们使用的类型不对,则会 `panic`\r\n```go\r\ntype Type interface {\r\n // Bits 以 bits 为单位返回类型的大小。\r\n // 如果类型的 Kind 不属于:sized 或者 unsized Int, Uint, Float, 或者 Complex,会 panic。\r\n Bits() int\r\n\r\n // ChanDir 返回一个通道类型的方向。\r\n // 如果类型的 Kind 不是 Chan,会 panic。\r\n ChanDir() ChanDir\r\n\r\n // IsVariadic 表示一个函数类型的最终输入参数是否为一个 \"...\" 可变参数。如果是,t.In(t.NumIn() - 1) 返回参数的隐式实际类型 []T.\r\n // 更具体的,如果 t 代表 func(x int, y ... float64),那么:\r\n // t.NumIn() == 2\r\n // t.In(0)是 \"int\" 的 reflect.Type 反射类型。\r\n // t.In(1)是 \"[]float64\" 的 reflect.Type 反射类型。\r\n // t.IsVariadic() == true\r\n // 如果类型的 Kind 不是 Func,IsVariadic 会 panic\r\n IsVariadic() bool\r\n\r\n // Elem 返回一个 type 的元素类型。\r\n // 如果类型的 Kind 不是 Array、Chan、Map、Ptr 或 Slice,就会 panic\r\n Elem() Type\r\n\r\n // Field 返回一个结构类型的第 i 个字段。\r\n // 如果类型的 Kind 不是 Struct,就会 panic。\r\n // 如果 i 不在 [0, NumField()) 范围内也会 panic。\r\n Field(i int) StructField\r\n\r\n // FieldByIndex 返回索引序列对应的嵌套字段。它相当于对每一个 index 调用 Field。\r\n // 如果类型的 Kind 不是 Struct,就会 panic。\r\n FieldByIndex(index []int) StructField\r\n\r\n // FieldByName 返回给定名称的结构字段和一个表示是否找到该字段的布尔值。\r\n FieldByName(name string) (StructField, bool)\r\n\r\n // FieldByNameFunc 返回一个能满足 match 函数的带有名称的 field 字段。布尔值表示是否找到。\r\n FieldByNameFunc(match func(string) bool) (StructField, bool)\r\n\r\n // In 返回函数类型的第 i 个输入参数的类型。\r\n // 如果类型的 Kind 不是 Func 类型会 panic。\r\n // 如果 i 不在 [0, NumIn()) 的范围内,会 panic。\r\n In(i int) Type\r\n\r\n // Key 返回一个 map 类型的 key 类型。\r\n // 如果类型的 Kind 不是 Map,会 panic。\r\n Key() Type\r\n\r\n // Len 返回一个数组类型的长度。\r\n // 如果类型的 Kind 不是 Array,会 panic。\r\n Len() int\r\n\r\n // NumField 返回一个结构类型的字段数目。\r\n // 如果类型的 Kind 不是 Struct,会 panic。\r\n NumField() int\r\n\r\n // NumIn 返回一个函数类型的输入参数数。\r\n // 如果类型的 Kind 不是Func.NumIn(),会 panic。\r\n NumIn() int\r\n\r\n // NumOut 返回一个函数类型的输出参数数。\r\n // 如果类型的 Kind 不是 Func.NumOut(),会 panic。\r\n NumOut() int\r\n\r\n // Out 返回一个函数类型的第 i 个输出参数的类型。\r\n // 如果类型的 Kind 不是 Func,会 panic。\r\n // 如果 i 不在 [0, NumOut()) 的范围内,会 panic。\r\n Out(i int) Type\r\n}\r\n```\r\n### 2.5.1. 创建reflect.Type的方法\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302204355928.png)\r\n## 2.6. 获取值信息reflect.Value\r\n`reflect.Value` 是一个结构体,它代表了一个值。 我们使用`reflect.Value`可以实现一些接收多种类型参数的函数,又或者可以让我们在运行时针对值的一些信息来进行修改。常常用在接收 `interface{}` 类型参数的方法中,因为参数是接口类型,所以我们可以通过 `reflect.ValueOf` 来获取到参数的值信息。 比如类型、大小、结构体字段、方法等等。\r\n- 设置值的方法:`Set*`:`Set`、`SetBool`、`SetBytes`、`SetCap`、`SetComplex`、`SetFloat`、`SetInt`、`SetLen`、`SetMapIndex`、`SetPointer`、`SetString`、`SetUint`。通过这类方法,我们可以修改反射值的内容,前提是这个反射值得是合适的类型。**CanSet 返回 true 才能调用这类方法**\r\n- 获取值的方法:`Interface`、`InterfaceData`、`Bool`、`Bytes`、`Complex`、`Float`、`Int`、`String`、`Uint`。通过这类方法,我们可以获取反射值的内容。前提是这个反射值是合适的类型,比如我们不能通过 `complex` 反射值来调用 `Int` 方法(我们可以通过 `Kind` 来判断类型)。\r\n- map 类型的方法:`MapIndex`、`MapKeys`、`MapRange`、`MapSet`。\r\n- chan 类型的方法:`Close`、`Recv`、`Send`、`TryRecv`、`TrySend`。\r\n- slice 类型的方法:`Len`、`Cap`、`Index`、`Slice`、`Slice3`。\r\n- struct 类型的方法:`NumField`、`NumMethod`、`Field`、`FieldByIndex`、`FieldByName`、`FieldByNameFunc`。\r\n- 判断是否可以设置为某一类型:`CanConvert`、`CanComplex`、`CanFloat`、`CanInt`、`CanInterface`、`CanUint`。\r\n- 方法类型的方法:`Method`、`MethodByName`、`Call`、`CallSlice`。\r\n- 判断值是否有效:`IsValid`。\r\n- 判断值是否是 `nil`:`IsNil`。\r\n- 判断值是否是零值:`IsZero`。\r\n- 判断值能否容纳下某一类型的值:`Overflow`、`OverflowComplex`、`OverflowFloat`、`OverflowInt`、`OverflowUint`。\r\n- 反射值指针相关的方法:`Addr`(`CanAddr` 为 `true` 才能调用)、`UnsafeAddr`、`Pointer`、`UnsafePointer`。\r\n- 获取类型信息:`Type`、`Kind`。\r\n- 获取指向元素的值:`Elem`。\r\n- 类型转换:`Convert`。\r\n### 2.6.1. 创建reflect.Value的方式\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302204622669.png)\r\n# 3. 示例\r\n## 3.1. 示例一\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"reflect\"\r\n)\r\n\r\nfunc main() {\r\n\tvar x float64 = 1.2345\r\n\r\n\tfmt.Println(\"==TypeOf==\")\r\n\tt := reflect.TypeOf(x)\r\n\tfmt.Println(\"type: \", t) // type: float64\r\n\tfmt.Println(\"kind:\", t.Kind()) // kind: float64\r\n\r\n\tfmt.Println(\"==ValueOf==\")\r\n\tv := reflect.ValueOf(x)\r\n\tfmt.Println(\"value: \", v) // value: 1.2345\r\n\tfmt.Println(\"type:\", v.Type()) // type: float64\r\n\tfmt.Println(\"kind:\", v.Kind()) // kind: float64\r\n\tfmt.Println(\"value:\", v.Float()) //value: 1.2345\r\n\tfmt.Println(v.Interface()) // 1.2345\r\n\tfmt.Printf(\"value is %5.2e\\n\", v.Interface()) // value is 1.23e+00\r\n\r\n\ty := v.Interface().(float64)\r\n\tfmt.Println(y) // 1.2345\r\n\r\n\tfmt.Println(\"===kind===\")\r\n\ttype MyInt int\r\n\tvar m MyInt = 5\r\n\tv = reflect.ValueOf(m)\r\n\tfmt.Println(\"kind:\", v.Kind()) // kind: int\r\n\tfmt.Println(\"type:\", v.Type()) // type: main.MyInt\r\n}\r\n```\r\n\r\n上面的例子,reflect 包中 reflect.TypeOf() 返回Type和 reflect.ValueOf() 返回Value类型 都有一个 Kind() 方法,Kind() 返回一个底层的数据类型,如 Unit,Float64,Slice, Int 等。\r\n\r\nreflect.ValueOf() 返回的 Value 类型:\r\n- 它有一个 Type() 方法,返回的是 reflect.Value 的 Type\r\n- 它有获取 Value 类型值的方法\r\n- 如果我们知道是 float 类型,所以直接用 Float() 方法。\r\n- 如果不知道具体类型呢?由上面例子可知用 Interface() 方法,然后在进行类型断言 v.Interface().(float64) 来判断获取值\r\n\r\nv.Kind() 和 v.Type() 区别:\r\n上例中,在 type MyInt int 里,v.Kind() 与 v.Type() 返回了不同的类型值,Kind()返回的是 int,Type() 返回的是 MyInt。\r\n在 Go 中,可以用type关键字定义自定义类型\r\n- Kind()方法返回底层类型。\r\n- 比如还有结构体,指针等类型用type定义的,那么 Kind() 方法就可以获取这些类型的底层类型。\r\n## 3.2. 示例二\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"reflect\"\r\n)\r\n\r\ntype student struct {\r\n\tName string `json:\"name\"`\r\n\tAge int `json:\"age\" id:\"1\"`\r\n}\r\n\r\nfunc main() {\r\n\tstu := student{\r\n\t\tName: \"hangmeimei\",\r\n\t\tAge: 15,\r\n\t}\r\n\r\n\tvalueOfStu := reflect.ValueOf(stu)\r\n\t// 获取struct字段数量\r\n\tfmt.Println(\"NumFields: \", valueOfStu.NumField())\r\n\t// 获取字段 Name 的值\r\n\tfmt.Println(\"Name value: \", valueOfStu.Field(0).String(), \", \", valueOfStu.FieldByName(\"Name\").String())\r\n\t// 字段类型\r\n\tfmt.Println(\"Name type: \", valueOfStu.Field(0).Type())\r\n\r\n\ttypeOfStu := reflect.TypeOf(stu)\r\n\tfor i := 0; i < typeOfStu.NumField(); i++ {\r\n\t\t// 获取字段名\r\n\t\tname := typeOfStu.Field(i).Name\r\n\t\tfmt.Println(\"Field Name: \", name)\r\n\r\n\t\t// 获取tag\r\n\t\tif fieldName, ok := typeOfStu.FieldByName(name); ok {\r\n\t\t\ttag := fieldName.Tag\r\n\r\n\t\t\tfmt.Println(\"tag-\", tag, \", \", \"json:\", tag.Get(\"json\"), \", id\", tag.Get(\"id\"))\r\n\t\t}\r\n\t}\r\n}\r\n```\r\n输出如下:\r\n```text\r\nNumFields: 2\r\nName value: hangmeimei , hangmeimei\r\nName type: string\r\nField Name: Name\r\ntag- json:\"name\" , json: name , id \r\nField Name: Age\r\ntag- json:\"age\" id:\"1\" , json: age , id 1\r\n```\r\n获取struct信息的一些方法:\r\n- NumField() 获取结构体字段数量\r\n- Field(i) 可以通过 i 字段索引来获取结构体字段信息,比如 Field(i).Name 获取字段名\r\n- FieldByName(name) 通过name获取字段信息\r\n## 3.3. 示例三\r\n反射调用方法\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"log\"\r\n\t\"reflect\"\r\n)\r\n\r\ntype PersonBehavior interface {\r\n\tSayName()\r\n\tSayAge()\r\n}\r\n\r\ntype Person struct {\r\n\tname string\r\n\tage int16\r\n}\r\n\r\nfunc (p Person) SayName() {\r\n\tlog.Println(p.name)\r\n}\r\n\r\nfunc (p Person) SayAge() {\r\n\tlog.Println(p.age)\r\n}\r\n\r\nfunc main() {\r\n\tvar p any = Person{\r\n\t\tname: \"lisi\",\r\n\t\tage: 12,\r\n\t}\r\n\r\n\tvalue := reflect.ValueOf(p)\r\n\r\n\tmethod := value.MethodByName(\"SayName\")\r\n\tif method.IsValid() {\r\n\t\tmethod.Call(nil)\r\n\t}\r\n\r\n\tfor i := range value.NumMethod() {\r\n\t\tmethod = value.Method(i)\r\n\t\tmethod.Call(nil)\r\n\t}\r\n}\r\n```\r\n输出如下\r\n```text\r\n2025/03/02 12:58:35 lisi\r\n2025/03/02 12:58:35 12\r\n2025/03/02 12:58:35 lisi\r\n```\r\n\r\n# 4. ref\r\nhttps://www.cnblogs.com/jiujuan/p/17142703.html"},{"id":"泛型","title":"泛型","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":15,"slug":"泛型","description":"1. 泛型 我们知道,函数的 形参(parameter) 只是类似占位符的东西并没有具体的值,只有我们调用函数传入实参(argument) 之后才有具体的值。那么,如果我们将形参 实参这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,问题就迎刃而解:在这里我们将其称之为 类型形参(typ...","relativePath":"Tech/Code/go/高级语法/泛型.md","rawContent":"# 1. 泛型\r\n我们知道,函数的 **形参(parameter)** 只是类似占位符的东西并没有具体的值,只有我们调用函数传入**实参(argument)** 之后才有具体的值。那么,如果我们将**形参 实参**这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,问题就迎刃而解:在这里我们将其称之为 **类型形参(type parameter)** 和 **类型实参(type argument)**,如下:\r\n```go\r\n// 假设 T 是类型形参,在定义函数时它的类型是不确定的,类似占位符\r\nfunc Add(a T, b T) T { \r\n return a + b\r\n}\r\n```\r\n在上面这段伪代码中, T被称为**类型形参(type parameter)**, 它不是具体的类型,在定义函数时类型并不确定。因为T的类型并不确定,所以我们需要像函数的形参那样,在调用函数的时候再传入具体的类型。这样我们不就能一个函数同时支持多个不同的类型了吗?在这里被传入的具体类型被称 **类型实参(type argument)**:\r\n\r\n下面一段伪代码展示了调用函数时传入类型实参的方式:\r\n```go\r\n// [T=int]中的 int 是类型实参,代表着函数Add()定义中的类型形参 T 全都被 int 替换\r\nAdd[T=int](100, 200) \r\n// 传入类型实参int后,Add()函数的定义可近似看成下面这样:\r\nfunc Add( a int, b int) int {\r\n return a + b\r\n}\r\n\r\n// 另一个例子:当我们想要计算两个字符串之和的时候,就传入string类型实参\r\nAdd[T=string](\"Hello\", \"World\") \r\n// 类型实参string传入后,Add()函数的定义可近似视为如下\r\nfunc Add( a string, b string) string {\r\n return a + b\r\n}\r\n```\r\n通过引入 **类型形参** 和 **类型实参** 这两个概念,我们让一个函数获得了处理多种不同类型数据的能力,这种编程方式被称为 **泛型编程**。\r\n## 1.1. 类型形参、类型实参、类型约束和泛型类型\r\n观察下面这个简单的例子:\r\n```go\r\ntype IntSlice []int\r\n\r\nvar a IntSlice = []int{1, 2, 3} // 正确\r\nvar b IntSlice = []float32{1.0, 2.0, 3.0} // ✗ 错误,因为IntSlice的底层类型是[]int,浮点类型的切片无法赋值\r\n```\r\n\r\n这里定义了一个新的类型 `IntSlice` ,它的底层类型是 `[]int` ,理所当然只有int类型的切片能赋值给 `IntSlice` 类型的变量。\r\n\r\n接下来如果我们想要定义一个可以容纳 `float32` 或 `string` 等其他类型的切片的话该怎么办?很简单,给每种类型都定义个新类型:\r\n\r\n```go\r\ntype StringSlice []string\r\ntype Float32Slie []float32\r\ntype Float64Slice []float64\r\n```\r\n\r\n但是这样做的问题显而易见,它们结构都是一样的只是成员类型不同就需要重新定义这么多新类型。那么有没有一个办法能只定义一个类型就能代表上面这所有的类型呢?答案是可以的,这时候就需要用到泛型了:\r\n\r\n```go\r\ntype Slice[T int|float32|float64 ] []T\r\n```\r\n\r\n不同于一般的类型定义,这里类型名称 `Slice` 后带了中括号,对各个部分做一个解说就是:\r\n- `T` 就是上面介绍过的**类型形参(Type parameter)**,在定义Slice类型的时候 T 代表的具体类型并不确定,类似一个占位符\r\n- `int|float32|float64` 这部分被称为**类型约束(Type constraint)**,中间的 `|` 的意思是告诉编译器,类型形参 T 只可以接收 int 或 float32 或 float64 这三种类型的实参\r\n- 中括号里的 `T int|float32|float64` 这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为 **类型形参列表(type parameter list)**\r\n- 这里新定义的类型名称叫 `Slice[T]`\r\n\r\n这种类型定义的方式中带了类型形参,很明显和普通的类型定义非常不一样,所以我们将这种\r\n> [!note]\r\n> 类型定义中带类型形参的类型,称之为泛型类型(Generic type)\r\n\r\n泛型类型不能直接拿来使用,必须传入**类型实参(Type argument)** 将其确定为具体的类型之后才可使用。而传入类型实参确定具体类型的操作被称为 **实例化(Instantiations)** :\r\n```go\r\n// 这里传入了类型实参int,泛型类型Slice[T]被实例化为具体的类型 Slice[int]\r\nvar a Slice[int] = []int{1, 2, 3} \r\nfmt.Printf(\"Type Name: %T\",a) //输出:Type Name: Slice[int]\r\n\r\n// 传入类型实参float32, 将泛型类型Slice[T]实例化为具体的类型 Slice[string]\r\nvar b Slice[float32] = []float32{1.0, 2.0, 3.0} \r\nfmt.Printf(\"Type Name: %T\",b) //输出:Type Name: Slice[float32]\r\n\r\n// ✗ 错误。因为变量a的类型为Slice[int],b的类型为Slice[float32],两者类型不同\r\na = b \r\n\r\n// ✗ 错误。string不在类型约束 int|float32|float64 中,不能用来实例化泛型类型\r\nvar c Slice[string] = []string{\"Hello\", \"World\"} \r\n\r\n// ✗ 错误。Slice[T]是泛型类型,不可直接使用必须实例化为具体的类型\r\nvar x Slice[T] = []int{1, 2, 3} \r\n```\r\n对于上面的例子,我们先给泛型类型 `Slice[T]` 传入了类型实参 `int` ,这样泛型类型就被实例化为了具体类型 `Slice[int]` ,被实例化之后的类型定义可近似视为如下:\r\n```go\r\ntype Slice[int] []int // 定义了一个普通的类型 Slice[int] ,它的底层类型是 []int\r\n```\r\n我们用实例化后的类型 `Slice[int]` 定义了一个新的变量 `a` ,这个变量可以存储int类型的切片。之后我们还用同样的方法实例化出了另一个类型 `Slice[float32]` ,并创建了变量 `b` 。\r\n因为变量 a 和 b 就是具体的不同类型了(一个 `Slice[int]` ,一个 `Slice[float32]`),所以 `a = b` 这样不同类型之间的变量赋值是不允许的。\r\n\r\n同时,因为 `Slice[T]`的类型约束限定了只能使用 int 或 float32 或 float64 来实例化自己,所以`Slice[string]`这样使用 string 类型来实例化是错误的。\r\n\r\n上面只是个最简单的例子,实际上类型形参的数量可以远远不止一个,如下:\r\n```go\r\n// MyMap类型定义了两个类型形参 KEY 和 VALUE。分别为两个形参指定了不同的类型约束\r\n// 这个泛型类型的名字叫: MyMap[KEY, VALUE]\r\ntype MyMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE \r\n\r\n// 用类型实参 string 和 flaot64 替换了类型形参 KEY 、 VALUE,泛型类型被实例化为具体的类型:MyMap[string, float64]\r\nvar a MyMap[string, float64] = map[string]float64{\r\n \"jack_score\": 9.6,\r\n \"bob_score\": 8.4,\r\n}\r\n```\r\n\r\n用上面的例子重新复习下各种概念的话:\r\n- KEY和VALUE是**类型形参**\r\n- `int|string` 是KEY的**类型约束**, `float32|float64` 是VALUE的**类型约束**\r\n- `KEY int|string, VALUE float32|float64` 整个一串文本因为定义了所有形参所以被称为**类型形参列表**\r\n- `Map[KEY, VALUE]`是**泛型类型**,类型的名字就叫 Map[KEY, VALUE]\r\n- `var a MyMap[string, float64] = xx` 中的string和float64是**类型实参**,用于分别替换KEY和VALUE,**实例化**出了具体的类型 `MyMap[string, float64]`\r\n总结:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503021752728.png)\r\n## 1.2. 其他的泛型类型\r\n所有类型定义都可使用类型形参,所以下面这种结构体以及接口的定义也可以使用类型形参:\r\n```go\r\n// 一个泛型类型的结构体。可用 int 或 sring 类型实例化\r\ntype MyStruct[T int | string] struct { \r\n Name string\r\n Data T\r\n}\r\n\r\n// 一个泛型接口(关于泛型接口在后半部分会详细讲解)\r\ntype IPrintData[T int | float32 | string] interface {\r\n Print(data T)\r\n}\r\n\r\n// 一个泛型通道,可用类型实参 int 或 string 实例化\r\ntype MyChan[T int | string] chan T\r\n```\r\n## 1.3. 类型形参的互相套用\r\n类型形参是可以互相套用的,如下\r\n```go\r\ntype WowStruct[T int | float32, S []T] struct {\r\n Data S\r\n MaxValue T\r\n MinValue T\r\n}\r\n```\r\n这个例子看起来有点复杂且难以理解,但实际上只要记住一点:任何泛型类型都必须传入类型实参实例化才可以使用。所以我们这就尝试传入类型实参看看:\r\n```go\r\nvar ws WowStruct[int, []int]\r\n// 泛型类型 WowStuct[T, S] 被实例化后的类型名称就叫 WowStruct[int, []int]\r\n```\r\n上面的代码中,我们为T传入了实参 `int`,然后因为 S 的定义是 `[]T` ,所以 S 的实参自然是 `[]int` 。经过实例化之后 WowStruct[T,S] 的定义类似如下:\r\n```go\r\n// 一个存储int类型切片,以及切片中最大、最小值的结构体\r\ntype WowStruct[int, []int] struct {\r\n Data []int\r\n MaxValue int\r\n MinValue int\r\n}\r\n```\r\n因为 S 的定义是`[]T`,所以 T 一定决定了的话 S 的实参就不能随便乱传了,下面这样的代码是错误的:\r\n```go\r\n// 错误。S的定义是[]T,这里T传入了实参int, 所以S的实参应当为 []int 而不能是 []float32\r\nws := WowStruct[int, []float32]{\r\n Data: []float32{1.0, 2.0, 3.0},\r\n MaxValue: 3,\r\n MinValue: 1,\r\n}\r\n```\r\n## 1.4. 泛型receiver\r\n看了上的例子,你一定会说,介绍了这么多复杂的概念,但好像泛型类型根本没什么用处啊。是的,单纯的泛型类型实际上对开发来说用处并不大。但是如果将泛型类型和接下来要介绍的泛型receiver相结合的话,泛型就有了非常大的实用性了\r\n我们知道,定义了新的普通类型之后可以给类型添加方法。那么可以给泛型类型添加方法吗?答案自然是可以的,如下:\r\n```go\r\ntype MySlice[T int | float32] []T\r\n\r\nfunc (s MySlice[T]) Sum() T {\r\n var sum T\r\n for _, value := range s {\r\n sum += value\r\n }\r\n return sum\r\n}\r\n```\r\n这个例子为泛型类型 `MySlice[T]` 添加了一个计算成员总和的方法 `Sum()` 。注意观察这个方法的定义:\r\n- 首先看receiver `(s MySlice[T])` ,所以我们直接把类型名称 `MySlice[T]` 写入了receiver中\r\n- 然后方法的返回参数我们使用了类型形参 T _*_*(实际上如果有需要的话,方法的接收参数也可以实用类型形参)\r\n- 在方法的定义中,我们也可以使用类型形参 T (在这个例子里,我们通过 `var sum T` 定义了一个新的变量 `sum` )\r\n\r\n对于这个泛型类型 `MySlice[T]` 我们该如何使用?还记不记得之前强调过很多次的,泛型类型无论如何都需要先用类型实参实例化,所以用法如下:\r\n```go\r\nvar s MySlice[int] = []int{1, 2, 3, 4}\r\nfmt.Println(s.Sum()) // 输出:10\r\n\r\nvar s2 MySlice[float32] = []float32{1.0, 2.0, 3.0, 4.0}\r\nfmt.Println(s2.Sum()) // 输出:10.0\r\n```\r\n该如何理解上面的实例化?首先我们用类型实参 int 实例化了泛型类型 `MySlice[T]`,所以泛型类型定义中的所有 T 都被替换为 int,最终我们可以把代码看作下面这样:\r\n```go\r\ntype MySlice[int] []int // 实例化后的类型名叫 MyIntSlice[int]\r\n\r\n// 方法中所有类型形参 T 都被替换为类型实参 int\r\nfunc (s MySlice[int]) Sum() int {\r\n var sum int \r\n for _, value := range s {\r\n sum += value\r\n }\r\n return sum\r\n}\r\n```\r\n\r\n用 float32 实例化和用 int 实例化同理,此处不再赘述。\r\n\r\n通过泛型receiver,泛型的实用性一下子得到了巨大的扩展。在没有泛型之前如果想实现通用的数据结构,诸如:堆、栈、队列、链表之类的话,我们的选择只有两个:\r\n- 为每种类型写一个实现\r\n- 使用接口+反射\r\n而有了泛型之后,我们就能非常简单地创建通用数据结构了。接下来用一个更加实用的例子 —— 队列来讲解\r\n\r\n### 1.4.1. 基于泛型的队列\r\n队列是一种先入先出的数据结构,它和现实中排队一样,数据只能从队尾放入、从队首取出,先放入的数据优先被取出来\r\n```go\r\n// 这里类型约束使用了空接口,代表的意思是所有类型都可以用来实例化泛型类型 Queue[T] (关于接口在后半部分会详细介绍)\r\ntype Queue[T interface{}] struct {\r\n elements []T\r\n}\r\n\r\n// 将数据放入队列尾部\r\nfunc (q *Queue[T]) Put(value T) {\r\n q.elements = append(q.elements, value)\r\n}\r\n\r\n// 从队列头部取出并从头部删除对应数据\r\nfunc (q *Queue[T]) Pop() (T, bool) {\r\n var value T\r\n if len(q.elements) == 0 {\r\n return value, true\r\n }\r\n\r\n value = q.elements[0]\r\n q.elements = q.elements[1:]\r\n return value, len(q.elements) == 0\r\n}\r\n\r\n// 队列大小\r\nfunc (q Queue[T]) Size() int {\r\n return len(q.elements)\r\n}\r\n```\r\n\r\n> 💡 为了方便说明,上面是队列非常简单的一种实现方法,没有考虑线程安全等很多问题\r\n\r\n`Queue[T]` 因为是泛型类型,所以要使用的话必须实例化,实例化与使用方法如下所示:\r\n```go\r\nvar q1 Queue[int] // 可存放int类型数据的队列\r\nq1.Put(1)\r\nq1.Put(2)\r\nq1.Put(3)\r\nq1.Pop() // 1\r\nq1.Pop() // 2\r\nq1.Pop() // 3\r\n\r\nvar q2 Queue[string] // 可存放string类型数据的队列\r\nq2.Put(\"A\")\r\nq2.Put(\"B\")\r\nq2.Put(\"C\")\r\nq2.Pop() // \"A\"\r\nq2.Pop() // \"B\"\r\nq2.Pop() // \"C\"\r\n\r\nvar q3 Queue[struct{Name string}] \r\nvar q4 Queue[[]int] // 可存放[]int切片的队列\r\nvar q5 Queue[chan int] // 可存放int通道的队列\r\nvar q6 Queue[io.Reader] // 可存放接口的队列\r\n// ......\r\n```\r\n\r\n### 1.4.2. 动态判断变量的类型\r\n使用接口的时候经常会用到类型断言或 type swith 来确定接口具体的类型,然后对不同类型做出不同的处理,如:\r\n```go\r\nvar i interface{} = 123\r\ni.(int) // 类型断言\r\n\r\n// type switch\r\nswitch i.(type) {\r\n case int:\r\n // do something\r\n case string:\r\n // do something\r\n default:\r\n // do something\r\n }\r\n}\r\n```\r\n\r\n那么你一定会想到,对于 `valut T` 这样通过类型形参定义的变量,我们能不能判断具体类型然后对不同类型做出不同处理呢?答案是不允许的,如下:\r\n\r\n```go\r\nfunc (q *Queue[T]) Put(value T) {\r\n value.(int) // 错误。泛型类型定义的变量不能使用类型断言\r\n\r\n // 错误。不允许使用type switch 来判断 value 的具体类型\r\n switch value.(type) {\r\n case int:\r\n // do something\r\n case string:\r\n // do something\r\n default:\r\n // do something\r\n }\r\n \r\n // ...\r\n}\r\n```\r\n虽然type switch和类型断言不能用,但我们可通过反射机制达到目的:\r\n```go\r\nfunc (receiver Queue[T]) Put(value T) {\r\n // Printf() 可输出变量value的类型(底层就是通过反射实现的)\r\n fmt.Printf(\"%T\", value) \r\n\r\n // 通过反射可以动态获得变量value的类型从而分情况处理\r\n v := reflect.ValueOf(value)\r\n\r\n switch v.Kind() {\r\n case reflect.Int:\r\n // do something\r\n case reflect.String:\r\n // do something\r\n }\r\n\r\n // ...\r\n}\r\n```\r\n这看起来达到了我们的目的,可是当你写出上面这样的代码时候就出现了一个问题:\r\n> 你为了避免使用反射而选择了泛型,结果到头来又为了一些功能在在泛型中使用反射\r\n \r\n当出现这种情况的时候你可能需要重新思考一下,自己的需求是不是真的需要用泛型(毕竟泛型机制本身就很复杂了,再加上反射的复杂度,增加的复杂度并不一定值得)\r\n## 1.5. 泛型函数\r\n在介绍完泛型类型和泛型receiver之后,我们来介绍最后一个可以使用泛型的地方——泛型函数。有了上面的知识,写泛型函数也十分简单。假设我们想要写一个计算两个数之和的函数:\r\n```go\r\nfunc Add(a int, b int) int {\r\n return a + b\r\n}\r\n```\r\n这个函数理所当然只能计算int的和,而浮点的计算是不支持的。这时候我们可以像下面这样定义一个泛型函数:\r\n```go\r\nfunc Add[T int | float32 | float64](a T, b T) T {\r\n return a + b\r\n}\r\n```\r\n上面就是泛型函数的定义。\r\n\r\n> 这种带类型形参的函数被称为**泛型函数**\r\n\r\n它和普通函数的点不同在于函数名之后带了类型形参。这里的类型形参的意义、写法和用法因为与泛型类型是一模一样的,就不再赘述了。\r\n\r\n和泛型类型一样,泛型函数也是不能直接调用的,要使用泛型函数的话必须传入类型实参之后才能调用。\r\n\r\n```go\r\nAdd[int](1,2) // 传入类型实参int,计算结果为 3\r\nAdd[float32](1.0, 2.0) // 传入类型实参float32, 计算结果为 3.0\r\n\r\nAdd[string](\"hello\", \"world\") // 错误。因为泛型函数Add的类型约束中并不包含string\r\n```\r\n\r\n或许你会觉得这样每次都要手动指定类型实参太不方便了。所以Go还支持类型实参的自动推导:\r\n\r\n```go\r\nAdd(1, 2) // 1,2是int类型,编译请自动推导出类型实参T是int\r\nAdd(1.0, 2.0) // 1.0, 2.0 是浮点,编译请自动推导出类型实参T是float32\r\n```\r\n\r\n自动推导的写法就好像免去了传入实参的步骤一样,但请记住这仅仅只是编译器帮我们推导出了类型实参,实际上传入实参步骤还是发生了的。\r\n\r\n### 1.5.1. 匿名函数不支持泛型\r\n\r\n在Go中我们经常会使用匿名函数,如:\r\n\r\n```go\r\nfn := func(a, b int) int {\r\n return a + b \r\n} // 定义了一个匿名函数并赋值给 fn \r\n\r\nfmt.Println(fn(1, 2)) // 输出: 3\r\n```\r\n\r\n那么Go支不支持匿名泛型函数呢?答案是不能——**匿名函数不能自己定义类型形参:**\r\n\r\n```go\r\n// 错误,匿名函数不能自己定义类型实参\r\nfnGeneric := func[T int | float32](a, b T) T {\r\n return a + b\r\n} \r\n\r\nfmt.Println(fnGeneric(1, 2))\r\n```\r\n\r\n但是匿名函数可以使用别处定义好的类型实参,如:\r\n\r\n```go\r\nfunc MyFunc[T int | float32 | float64](a, b T) {\r\n\r\n // 匿名函数可使用已经定义好的类型形参\r\n fn2 := func(i T, j T) T {\r\n return i*2 - j*2\r\n }\r\n\r\n fn2(a, b)\r\n}\r\n```\r\n\r\n### 1.5.2. 既然支持泛型函数,那么泛型方法呢?\r\n\r\n既然函数都支持泛型了,那你应该自然会想到,方法支不支持泛型?很不幸,目前Go的方法并不支持泛型,如下:\r\n\r\n```go\r\ntype A struct {\r\n}\r\n\r\n// 不支持泛型方法\r\nfunc (receiver A) Add[T int | float32 | float64](a T, b T) T {\r\n return a + b\r\n}\r\n```\r\n\r\n但是因为receiver支持泛型, 所以如果想在方法中使用泛型的话,目前唯一的办法就是曲线救国,迂回地通过receiver使用类型形参:\r\n\r\n```go\r\ntype A[T int | float32 | float64] struct {\r\n}\r\n\r\n// 方法可以使用类型定义中的形参 T \r\nfunc (receiver A[T]) Add(a T, b T) T {\r\n return a + b\r\n}\r\n\r\n// 用法:\r\nvar a A[int]\r\na.Add(1, 2)\r\n\r\nvar aa A[float32]\r\naa.Add(1.0, 2.0)\r\n```\r\n\r\n# 2. ref\r\nhttps://segmentfault.com/a/1190000041634906"},{"id":"http连接池","title":"Http连接池","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":5,"slug":"http连接池","description":"1. 长连接 长连接利用keep-alive技术实现,能在多次 HTTP 之间重用同一个 TCP 连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等)。 然而长连接并非没有弊端,天下没有免费的午餐,如果客户端在接收完所有的信息之后还没有关闭连接,则服务端相应的资...","relativePath":"Tech/Code/http连接池.md","rawContent":"# 1. 长连接\r\n长连接利用keep-alive技术实现,能在多次 HTTP 之间重用同一个 TCP 连接,从而**减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等)**。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241218234752650.png)\r\n\r\n然而长连接并非没有弊端,天下没有免费的午餐,如果客户端在接收完所有的信息之后还没有关闭连接,则服务端相应的资源还在被占用(尽管已经没用了)。例如Tomcat的BIO实现中,未关闭的连接会占用对应的处理线程,如果一个长连接实际上已经处理完毕,但关闭的超时时间未到,则该线程会一直被占用(使用NIO的实现没有该问题)。显然,**如果客户端和服务端的确需要进行多次通信,则开启 keep-alive 是更好的选择**,例如在微服务架构中,通常微服务的使用方和提供方会长期有交流。在一些 TPS/QPS 很高的 REST 服务中,如果使用的是短连接(即没有开启keep-alive),则很可能发生客户端端口被占满的情形。这是由于短时间内会创建大量TCP 连接,而在 TCP 四次挥手结束后,客户端的端口会处于TIME_WAIT一段时间(2MSL,linux系统中大概`60*2=120`秒),这期间端口不会被释放,从而导致端口被占满。这种情况下最好使用长连接。\r\n## 1.1. 开启方法\r\n- 在 HTTP1.0中,若要使用长连接,需要在请求头中明确添加 “Connection: keep-alive”。只有当客户端和服务器都设置了该字段时,才会建立长连接。客户端发出带有 “Connection: keep-alive” 头的请求;服务端接收到这个请求后,根据 HTTP 1.0 和该头信息判断出这是一个长连接,会在响应头中也增加 “Connection: keep-alive”,并且不会关闭已建立的 TCP 连接;客户端收到服务端的响应后,发现其中包含 “Connection: keep-alive”,就认为是一个长连接,不关闭这个连接。\r\n- HTTP 1.1 默认支持长连接,无需额外设置 “Connection: keep-alive”。\r\n## 1.2. LB长连接\r\nLB的前后端连接是独立的,长连接的维护也是独立的,会有前后端两个独立的连接池。即使客户端与LB在一个长连接会话中发起多次请求,这些请求也会被负载均衡到多个LB与后端server的连接中完成。\r\n## 1.3. http1.0长连接测试\r\nserver端代码\r\n```python\r\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\r\nimport time\r\n\r\n\r\nclass LongConnectionHandler(BaseHTTPRequestHandler):\r\n protocol_version = \"HTTP/1.0\"\r\n\r\n def do_GET(self):\r\n self.send_response(200)\r\n self.send_header('Content-type', 'text/html')\r\n self.send_header('Connection', 'keep-alive') # 设置响应头,表明支持长连接\r\n self.end_headers()\r\n self.wfile.write(b\"

Hello, World!

\")\r\n\r\n\r\nif __name__ == \"__main__\":\r\n server_address = ('', 8000)\r\n httpd = HTTPServer(server_address, LongConnectionHandler)\r\n print('Starting server, use to stop')\r\n httpd.serve_forever()\r\n```\r\nclient端代码(这里裸写的socket编程,真正的HTTP client需要看响应头中是否携带keep-alive来决定要不要保持长连接,使用如下代码后是否保持长连接就只取决于服务端行为了)\r\n```python\r\nimport socket\r\nimport time\r\n\r\ndef http_1_0_client():\r\n host = \"localhost\"\r\n port = 8000\r\n buffer_size = 10240\r\n\r\n # 创建套接字并连接到服务器\r\n client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r\n client_socket.connect((host, port))\r\n\r\n # 构造HTTP 1.0请求消息,设置长连接头部\r\n request = \"GET / HTTP/1.0\\r\\n\"\r\n request += \"Host: localhost\\r\\n\"\r\n request += \"Connection: Keep-Alive\\r\\n\\r\\n\"\r\n\r\n # 发送第一次请求\r\n client_socket.send(request.encode())\r\n # 等待服务端响应\r\n time.sleep(1)\r\n response = client_socket.recv(buffer_size).decode()\r\n print(\"第一次响应:\")\r\n print(response)\r\n\r\n time.sleep(10)\r\n\r\n # 构造并发送第二次请求(可以多次重复类似操作体现长连接持续交互)\r\n second_request = \"GET / HTTP/1.0\\r\\n\"\r\n second_request += \"Host: localhost\\r\\n\"\r\n second_request += \"Connection: Keep-Alive\\r\\n\\r\\n\"\r\n client_socket.send(second_request.encode())\r\n # 等待服务端响应\r\n time.sleep(1)\r\n second_response = client_socket.recv(buffer_size).decode()\r\n print(\"第二次响应:\")\r\n print(second_response)\r\n client_socket.close()\r\n\r\n\r\nif __name__ == \"__main__\":\r\n http_1_0_client()\r\n```\r\n可以看到在此期间连接一直处于established状态,客户端的两次请求复用了同一个tcp连接。\r\n```shell\r\n➜ ~ netstat -anv|grep 8000\r\ntcp4 0 0 127.0.0.1.8000 127.0.0.1.64622 ESTABLISHED 408241 146988 48143 0 0x0002 0x00000004\r\ntcp4 48 0 127.0.0.1.64622 127.0.0.1.8000 ESTABLISHED 408160 146988 48145 0 0x0002 0x00000000\r\ntcp4 0 0 *.8000 *.* LISTEN 131072 131072 48143 0 0x0000 0x00000006\r\n```\r\n## 1.4. http1.1长连接测试\r\nhttp1.1下不需要携带keep-alive头,默认使用长连接。Client或者Server都可以通过设置请求头或响应头`\"Connection\": \"close\"`方式强制关闭。\r\n\r\nserver端代码\r\n```python\r\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\r\nimport time\r\n\r\n\r\nclass LongConnectionHandler(BaseHTTPRequestHandler):\r\n protocol_version = \"HTTP/1.1\"\r\n\r\n def do_GET(self):\r\n self.send_response(200)\r\n self.send_header('Content-type', 'text/html')\r\n self.end_headers()\r\n self.wfile.write(b\"

Hello, World!

\")\r\n\r\n\r\nif __name__ == \"__main__\":\r\n server_address = ('', 8000)\r\n httpd = HTTPServer(server_address, LongConnectionHandler)\r\n print('Starting server, use to stop')\r\n httpd.serve_forever()\r\n```\r\nclient端代码\r\n```python\r\nimport socket\r\nimport time\r\n\r\ndef http_1_0_client():\r\n host = \"localhost\"\r\n port = 8000\r\n buffer_size = 10240\r\n\r\n # 创建套接字并连接到服务器\r\n client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r\n client_socket.connect((host, port))\r\n\r\n # 构造HTTP 1.1请求消息,默认长连接\r\n request = \"GET / HTTP/1.1\\r\\n\"\r\n request += \"Host: localhost\\r\\n\\r\\n\"\r\n\r\n # 发送第一次请求\r\n client_socket.send(request.encode())\r\n # 等待服务端响应\r\n time.sleep(1)\r\n response = client_socket.recv(buffer_size).decode()\r\n print(\"第一次响应:\")\r\n print(response)\r\n\r\n time.sleep(10)\r\n\r\n # 构造并发送第二次请求(可以多次重复类似操作体现长连接持续交互)\r\n second_request = \"GET / HTTP/1.1\\r\\n\"\r\n second_request += \"Host: localhost\\r\\n\\r\\n\"\r\n client_socket.send(second_request.encode())\r\n # 等待服务端响应\r\n time.sleep(1)\r\n second_response = client_socket.recv(buffer_size).decode()\r\n print(\"第二次响应:\")\r\n print(second_response)\r\n client_socket.close()\r\n\r\n\r\nif __name__ == \"__main__\":\r\n http_1_0_client()\r\n```\r\n\r\n## 1.5. 长链接超时设置\r\n- 客户端和服务端都可以独立声明连接的生命周期\r\n- 客户端超时时间建议设置比服务端短\r\n- 客户端到服务端中间有可能会有7层LB,此时LB前后端的连接是独立的。==建议客户端超时时间

Hello, World!

\")`处断点,然后执行client代码,等待server执行的断点处时,强制kill client端,此时继续执行server端代码输出响应体,server端就会报错`ConnectionResetError: [Errno 54] Connection reset by peer`\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241219224340496.png)\r\n\r\n### 2.1.3. Broken pipe\r\nBroken pipe实际上是一个不应该出现的错误,甚至可以理解为是一个bug,表明了你在往一个已经关闭的socket中写数据。当对端因为种种原因需要强制关闭连接的时候会发送RST,这时候应用捕获会报错Connection reset by peer,而当你不顾这个错误,继续写的时候,就会报错Broken pipe。\r\n\r\n### 2.1.4. Connection refused\r\n1. **服务未启动**:比如你尝试访问一个Web服务器,但Web服务器软件没有运行起来,那么在它对应的端口(如80端口用于HTTP服务)就没有服务在监听,此时客户端就会收到“Connection refused”的反馈。\r\n2. **防火墙阻止**:防火墙规则可能会阻止客户端对特定端口的访问。即使服务器在正常运行并且监听了相应端口,但防火墙设置可能会导致连接请求被拒绝,客户端同样会收到“Connection refused”的消息。\r\n在不启动server的情况下,直接启动client发送请求,可以发现tcp第一次握手后,直接被server发送RST拒绝了,此时客户端会报错`ConnectionRefusedError: [Errno 61] Connection refused`。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241219224619720.png)\r\n\r\n### 2.1.5. 502 Bad Gateway\r\n不建议多个LB挂相同的后端节点,对于客户端来说,看到的是不同的目的IP,此时可能源端口会复用。第二个请求发送的syn包对服务端来说是不可接受的,因此server端发送rst将连接中断,从而导致两个请求都失败(也有请求是,发送syn后立马收到了server对于第一个连接的ack或者psh报文,这时候client也会认为异常,从而发送rst到服务端)。这种情况下,LB都无法与后端服务建立正常连接,因此会报错返回502。\r\n\r\nLB节点超时时间比后端Server超时时间要长,有概率导致LB使用了已经被后端关闭的连接,此时也会建链失败,从而报错502。\r\n\r\n# 3. 连接池\r\nHttpClient中使用了连接池来管理持有连接,同一条 TCP 链路上,连接是可以复用的。HttpClient 通过连接池的方式进行连接持久化。其实“池”技术是一种通用的设计,其设计思想并不复杂:\r\n1. 当有连接第一次使用的时候建立连接\r\n2. 结束时对应连接不关闭,归还到池中\r\n3. 下次同个目的的连接可从池中获取一个可用连接\r\n4. 定期清理过期连接\r\n\r\n\r\n# 4. reference\r\nhttps://blog.csdn.net/rickiyeat/article/details/107900585?sharetype=blogdetail&shareId=107900585&sharerefer=APP&sharesource=jstxzhangrui&sharefrom=link\r\n\r\n![[HTTP协议解析]]"},{"id":"java","title":"Java","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"java","description":"","relativePath":"Tech/Code/java/java.md","rawContent":""},{"id":"java9-java17新特性","title":"Java9 Java17新特性","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"java9-java17新特性","description":"1. 集合 增加 了 List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。 2. 私有方法 java8中允许接口有默认方法,java9中允许接口有默认私有方法 3. Optioal的ifPresentOrElse 4. 类型推断 类型...","relativePath":"Tech/Code/java/java9-java17新特性.md","rawContent":"# 1. 集合\r\n增加 了 List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。\r\n```java\r\nList list = List.of(1, 2, 3, 4, 5); \r\nSet set = Set.of(1, 2, 3, 4, 5); \r\nMap map = Map.of(\"a\", 1, \"b\", 2, \"c\", 3, \"d\", 4);\r\n```\r\n# 2. 私有方法\r\njava8中允许接口有默认方法,java9中允许接口有默认私有方法\r\n# 3. Optioal的ifPresentOrElse\r\n```java\r\nOptional.of(1).ifPresentOrElse( \r\n System.out::println, \r\n () -> System.out.println(\"not exist.\") \r\n);\r\n```\r\n# 4. 类型推断\r\n类型只是个语法糖,定义的变量在编译期就必须确定类型,不能用作函数入参或返回值\r\n```java\r\nvar map = Map.of(\"a\", 1); \r\nvar str = \"hello\";\r\n```\r\n# 5. instanceof模式匹配\r\n```java\r\npublic static void testType(Object str) { \r\n if (str instanceof String s) { \r\n System.out.println(\"string is \" + s); \r\n } else if (str instanceof Integer i) { \r\n System.out.println(\"integer is \" + i); \r\n } \r\n}\r\n```\r\n# 6. switch表达式扩展\r\n引入了类似lambda语法条件,支持多值匹配,匹配成功后的执行块不需要break\r\n```java\r\nfor (int i = 0; i < 10; i++) { \r\n switch (i) { \r\n case 0,1,8,9 -> System.out.println(\"i小于2或大于7\"); \r\n default -> System.out.println(\"i大于等于2且小于等于7\"); \r\n } \r\n}\r\n/**\r\ni小于2或大于7\r\ni小于2或大于7\r\ni大于等于2且小于等于7\r\ni大于等于2且小于等于7\r\ni大于等于2且小于等于7\r\ni大于等于2且小于等于7\r\ni大于等于2且小于等于7\r\ni大于等于2且小于等于7\r\ni小于2或大于7\r\ni小于2或大于7\r\n**/\r\n```\r\nswich作为表达式\r\n```java\r\nvar score = 'C'; \r\nString s = switch (score){ \r\n case 'A' -> \"优秀\"; \r\n case 'B' -> \"良好\"; \r\n case 'C' -> { \r\n System.out.println(\"do something\"); \r\n // 通过yield返回值 \r\n yield \"中\"; \r\n } \r\n case 'D' -> \"及格\"; \r\n case 'F' -> \"不及格\"; \r\n default -> \"成绩输入错误\"; \r\n}; \r\n```\r\n1. 和`instanceof` 一样, `switch` 也紧跟着增加了类型匹配自动转换功能。\r\n2. case可以匹配null\r\n```java\r\n// Old code \r\nstatic String formatter(Object o) { \r\n String formatted = \"unknown\"; \r\n if (o == null) { \r\n formatted = \"null\"; \r\n } else if (o instanceof String s) { \r\n formatted = String.format(\"String %s\", s); \r\n } else if (o instanceof Integer i) { \r\n formatted = String.format(\"int %d\", i); \r\n } else if (o instanceof Long l) { \r\n formatted = String.format(\"long %d\", l); \r\n } else if (o instanceof Double d) { \r\n formatted = String.format(\"double %f\", d); \r\n } else { \r\n formatted = o.toString(); \r\n } \r\n if (o instanceof Integer i) { \r\n formatted = String.format(\"int %d\", i); \r\n } else if (o instanceof Long l) { \r\n formatted = String.format(\"long %d\", l); \r\n } else if (o instanceof Double d) { \r\n formatted = String.format(\"double %f\", d); \r\n } else if (o instanceof String s) { \r\n formatted = String.format(\"String %s\", s); \r\n } \r\n return formatted; \r\n} \r\n \r\n// New code \r\nstatic String formatterPatternSwitch(Object o) { \r\n return switch (o) { \r\n case null -> \"null\"; \r\n case Integer i -> String.format(\"int %d\", i); \r\n case Long l -> String.format(\"long %d\", l); \r\n case Double d -> String.format(\"double %f\", d); \r\n case String s -> String.format(\"String %s\", s); \r\n default -> o.toString(); \r\n }; \r\n}\r\n```\r\n# 7. 文本块\r\n```java\r\nvar str = \"\"\" \r\n this is the first line \r\n this is the second line \r\n this is the third line \r\n \"\"\"; \r\nvar json = \"\"\" \r\n { \r\n \"firstName\": \"Piotr\", \r\n \"lastName\": \"Mińkowski\" \r\n } \r\n \"\"\";\r\n/**\r\n编译后结果\r\nString str = \"this is the first line\\nthis is the second line\\nthis is the third line\\n\"; \r\nString json = \"{\\n \\\"firstName\\\": \\\"Piotr\\\",\\n \\\"lastName\\\": \\\"Mińkowski\\\"\\n}\\n\";\r\n**/\r\n```\r\n\r\n# 8. record类\r\n```java\r\npublic record Point(int x, int y) { \r\n public Point(int x, int y) { \r\n // 这是我们编写的Compact Constructor: \r\n if (x < 0 || y < 0) { \r\n throw new IllegalArgumentException(); \r\n } \r\n // 这是编译器继续生成的赋值代码: \r\n this.x = x; \r\n this.y = y; \r\n } \r\n \r\n // 可以自定义成员方法或静态方法 \r\n public static Point of(int x, int y) { \r\n return new Point(x, y); \r\n } \r\n}\r\n```\r\n等效于下面这个类\r\n1. 它是一个`final`类\r\n2. 自动实现`equals`、`hashCode`、`toString`函数\r\n3. 自动生成filed()方法获取成员属性\r\n```java\r\nfinal class Point extends Record {\r\n private final int x;\r\n private final int y;\r\n\r\n public Point(int x, int y) {\r\n this.x = x;\r\n this.y = y;\r\n }\r\n\r\n public int x() {\r\n return this.x;\r\n }\r\n\r\n public int y() {\r\n return this.y;\r\n }\r\n\r\n public String toString() {\r\n return String.format(\"Point[x=%s, y=%s]\", x, y);\r\n }\r\n\r\n\t@Override \r\n\tpublic boolean equals(Object o) { \r\n\t if (this == o) return true; \r\n\t if (o == null || getClass() != o.getClass()) return false; \r\n\t Point point = (Point) o; \r\n\t return x == point.x && y == point.y; \r\n\t} \r\n\t \r\n\t@Override \r\n\tpublic int hashCode() { \r\n\t return Objects.hash(x, y); \r\n\t}\r\n}\r\n```\r\n测试输出\r\n```java\r\nPoint point1 = Point.of(1, 2); \r\nPoint point2 = Point.of(1, 2); \r\nSystem.out.println(point1); \r\nSystem.out.println(point1.equals(point2));\r\n/**\r\nPoint[x=1, y=2]\r\ntrue\r\n**/\r\n```\r\n# 9. 密封类\r\n\r\n| 关键字 | 作用 | 继承限制 |\r\n| ------------ | ----------- | ----------------- |\r\n| `sealed` | 限制继承为指定子类 | 仅允许`permits`列表中的类 |\r\n| `non-sealed` | 解除密封,允许自由继承 | 无限制 |\r\n| `final` | 禁止任何继承 | 不可被继承 |\r\n## 9.1. sealed\r\n- **限制继承/实现**:`sealed`类或接口只能被指定的子类继承(类)或实现(接口)。\r\n- **明确层级结构**:通过`permits`子句列出所有允许的直接子类 / 实现类,确保继承关系可控。\r\n```java\r\n// 定义一个sealed接口,仅允许Circle和Rectangle实现\r\npublic sealed interface Shape permits Circle, Rectangle, ColorfulShape {\r\n double area();\r\n}\r\n\r\n// 具体实现类必须声明为final、sealed或non-sealed\r\npublic final class Circle implements Shape { ... }\r\npublic sealed class Rectangle implements Shape permits Square { ... }\r\n```\r\n如果允许扩展的子类和封闭类在同一个源代码文件里,封闭类可以不使用 permits 语句,Java 编译器将检索源文件,在编译期为封闭类添加上许可的子类。\r\n```java\r\npackage com.shinerio.tutorial.jdk;\r\n\r\npublic sealed class ColorfulShape implements Shape {\r\n static final class CircleColorfulShape extends ColorfulShape {\r\n }\r\n}\r\n\r\nfinal class RectangleColorfulShape extends ColorfulShape {\r\n}\r\n```\r\n## 9.2. non-sealed\r\n- **解除密封限制**:允许`sealed`类的子类被其他类继续继承(即打破`sealed`的封闭性)。\r\n```java\r\n// Rectangle是sealed类,但允许Square继承\r\npublic sealed class Rectangle implements Shape permits Square { ... }\r\n\r\n// Square声明为non-sealed,允许其他类继续继承\r\npublic non-sealed class Square extends Rectangle { ... }\r\n\r\n// 其他类可以继承Square\r\npublic class ColoredSquare extends Square { ... }\r\n```"},{"id":"jni","title":"Jni","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"jni","description":"会在指定的所有目录下查找名为 libnativememoryutils.so(Linux)或 nativememoryutils.dll(Windows)的文件。 > [!note] > 在 Linux 下,文件名必须是 libnativememoryutils.so,不能省略 lib 前缀。 1....","relativePath":"Tech/Code/java/jni.md","rawContent":"`System.loadLibrary(\"native_memory_utils\")` 会在`java.library.path`指定的所有目录下查找名为 libnative_memory_utils.so(Linux)或 native_memory_utils.dll(Windows)的文件。\r\n> [!note]\r\n> 在 Linux 下,文件名必须是 libnative_memory_utils.so,不能省略 lib 前缀。\r\n# 1. 编译\r\n定义java方法\r\n```java\r\npublic class NativeMemoryUtils {\r\n    static {\r\n        System.loadLibrary(\"native_memory_utils\");\r\n    }\r\n\r\n    public static native long allocateAndFillMemory(long size, String fill);\r\n    public static native void freeMemory(long address);\r\n}\r\n```\r\n生成头文件\r\n```shell\r\njavac -h . src/main/java/com/example/demo/NativeMemoryUtils.java\r\n```\r\n编写 native_memory_utils.c\r\n```c\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \"com_example_demo_NativeMemoryUtils.h\"\r\n\r\nJNIEXPORT jlong JNICALL Java_com_example_demo_NativeMemoryUtils_allocateAndFillMemory\r\n (JNIEnv *env, jclass clazz, jlong size, jstring fill) {\r\n const char *fillStr = (*env)->GetStringUTFChars(env, fill, 0);\r\n size_t fillLen = strlen(fillStr);\r\n if (fillLen == 0) fillLen = 1;\r\n\r\n char *mem = (char *)malloc((size_t)size);\r\n if (!mem) {\r\n (*env)->ReleaseStringUTFChars(env, fill, fillStr);\r\n return 0;\r\n }\r\n for (jlong i = 0; i < size; ++i) {\r\n mem[i] = fillStr[i % fillLen];\r\n }\r\n (*env)->ReleaseStringUTFChars(env, fill, fillStr);\r\n return (jlong)(intptr_t)mem;\r\n}\r\n\r\nJNIEXPORT void JNICALL Java_com_example_demo_NativeMemoryUtils_freeMemory\r\n (JNIEnv *env, jclass clazz, jlong address) {\r\n if (address != 0) {\r\n free((void *)(intptr_t)address);\r\n }\r\n} \r\n```\r\n编译为so\r\n```\r\ngcc -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -shared -o libnative_memory_utils.so native_memory_utils.c\r\n```\r\n# 2. 默认查找路径\r\n- 当前工作目录\r\n- JRE 的 lib 目录\r\n- 环境变量`LD_LIBRARY_PATH`指定的目录\r\n- 启动 JVM 时`-Djava.library.path=`指定的目录\r\n# 3. 匹配逻辑\r\n不要直接写绝对路径,System.loadLibrary 只接受库名,不接受路径。如natvie_memory_utils对应libnative_memory_utils.so\r\n"},{"id":"mtls实现","title":"Mtls实现","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"mtls实现","description":"1. 生成证书和密钥库 1.1. openssl配置 openssl中可以通过--config指定完整的配置文件,包含所有配置段(sections),用于生成包含扩展字段的证书签名请求(CSR)或自签名证书。 1.2. 生成客户端证书和服务端证书 2. 本地安装ca证书和客户端证书 windows可...","relativePath":"Tech/Code/java/mtls实现.md","rawContent":"# 1. 生成证书和密钥库\r\n## 1.1. openssl配置\r\nopenssl中可以通过--config指定**完整的配置文件**,包含所有配置段(sections),用于生成包含扩展字段的证书签名请求(CSR)或自签名证书。\r\n```conf\r\n[req]\r\ndistinguished_name = req_distinguished_name # 指定DN信息的配置段名\r\nreq_extensions = v3_req # 指定扩展字段的配置段名\r\nprompt = no # 不提示用户输入,直接使用配置文件中的值\r\n\r\n[req_distinguished_name]\r\nC = CN # Country - 国家代码:中国\r\nST = Beijing # State/Province - 省/州:北京\r\nL = Beijing # Locality - 城市:北京 \r\nO = shinerio # Organization - 组织名:shinerio\r\nOU = IT Department # Organizational Unit - 部门:IT部门\r\nCN = shinerio.site # Common Name - 通用名称:shinerio.site(主域名)\r\n\r\n[v3_req]\r\n# 密钥用途: # - digitalSignature: 数字签名(用于身份验证)\r\n# - keyEncipherment: 密钥加密(用于加密对称密钥) \r\n# - dataEncipherment: 数据加密(用于直接加密数据)\r\nkeyUsage = digitalSignature, keyEncipherment, dataEncipherment\r\n# 扩展密钥用途: # - serverAuth: TLS/SSL服务器身份验证\r\nextendedKeyUsage = serverAuth\r\n# 主题备用名称:引用alt_names段的配置\r\nsubjectAltName = @alt_names\r\n\r\n# 现代浏览器主要检查SAN\r\n# Subject Alternative Name (SAN) 是X.509证书中的一个扩展字段,用于指定证书可以保护的额外域名、IP地址或其他标识符。CN只能指定一个主域名;SAN可以保护多个域名\r\n[alt_names]\r\nDNS.1 = shinerio.site\r\nDNS.2 = web.shinerio.site\r\nDNS.3 = localhost\r\nDNS.4 = *.shinerio.site\r\nIP.1 = 127.0.0.1\r\nIP.2 = ::1\r\n```\r\n## 1.2. 生成客户端证书和服务端证书\r\n```shell \r\n# 生成CA密钥和证书 \r\nopenssl genrsa -out ca.key 2048 \r\nopenssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj \"/CN=shinerio-CA\" \r\n \r\n# 生成服务器密钥和证书签名请求,指定server.conf文件,通过san指定了服务域名,否则浏览器会不信任域名。\r\nopenssl genrsa -out server.key 2048 \r\nopenssl req -new -key server.key -out server.csr -config server.conf \r\n# 使用CA签署服务器证书 \r\nopenssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -extensions v3_req -extfile server.conf \r\n# 创建服务器密钥库 \r\nopenssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -name server -CAfile ca.crt -caname root -passout pass:changeit \r\nkeytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore server.jks -deststoretype JKS -srcstorepass changeit -deststorepass changeit \r\n# 创建服务器信任库,包含ca证书 \r\nkeytool -import -alias ca -file ca.crt -keystore server_truststore.jks -storepass changeit \r\n \r\n# 生成客户端密钥和证书签名请求 \r\nopenssl genrsa -out client.key 2048 \r\nopenssl req -new -key client.key -out client.csr -subj \"/CN=Shinerio-Client\" \r\n# 使用CA签署客户端证书 \r\nopenssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 3650 \r\n# 创建客户端密钥库,用于本地导入,实现客户端认证 \r\nopenssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name client -CAfile ca.crt -caname root -passout pass:changeit \r\nkeytool -importkeystore -srckeystore client.p12 -srcstoretype PKCS12 -destkeystore client.jks -deststoretype JKS -srcstorepass changeit -deststorepass changeit \r\n# 创建客户端信任库,包含ca证书 \r\nkeytool -import -alias ca -file ca.crt -keystore client_truststore.jks -storepass changeit \r\n``` \r\n\r\n# 2. 本地安装ca证书和客户端证书 \r\nwindows可以直接双击client.p12文件,导入到windows证书库中,浏览器访问指定网页时会弹框要求选择证书。 \r\n![img.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506122306764.png)\r\n> [!note]\r\n> 在windows运行,建议使用git提供的`/c/Program\\ Files/Git/usr/bin/openssl.exe`工具生成p12证书。 linux服务器下生成的p12文件可能无法被windows导入。 \r\n\r\n为了浏览器访问的时候,不提示风险,我们还需要把我们生成CA证书导入到系统受信任根证书颁发机构中。 \r\n![img_1.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506122306155.png)\r\n\r\n# 3. 客户端 \r\n```java \r\n// 加载客户端证书、私钥 KeyStore keyStore = loadKeyStore(\"client.jks\", \"changeit\"); KeyStore trustStore = loadKeyStore(\"client_truststore.jks\", \"changeit\"); \r\n \r\n// 创建KeyManagerFactory KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, \"changeit\".toCharArray()); \r\n \r\n// 创建TrustManagerFactory TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); \r\n \r\n// 创建SSLContext SSLContext sslContext = SSLContext.getInstance(\"TLS\"); sslContext.init( \r\n kmf.getKeyManagers(), \r\n tmf.getTrustManagers(), \r\nnew SecureRandom() ); \r\n \r\n// 创建带有mTLS配置的HttpClient HttpClient client = HttpClient.newBuilder() \r\n .sslContext(sslContext) \r\n .sslParameters(createSSLParameters()) \r\n .build(); \r\n \r\n// 发送HTTP请求 HttpRequest request = HttpRequest.newBuilder() \r\n .uri(URI.create(\"https://localhost:8443/hello?name=Alice\")) \r\n .GET() \r\n .build(); \r\n \r\nHttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); \r\n \r\n// 打印响应 System.out.println(\"Status code: \" + response.statusCode()); System.out.println(\"Response body: \" + response.body()); \r\n``` \r\n \r\n核心在于sslContext.init方法,参数`km`用于像服务端发送自己的证书。 \r\n```java \r\n /** * Initializes this context. Either of the first two parameters * may be null in which case the installed security providers will * be searched for the highest priority implementation of the * appropriate factory. Likewise, the secure random parameter may * be null in which case the default implementation will be used. * * @param km the sources of authentication keys or null * @param tm the sources of peer authentication trust decisions or null * @param random the source of randomness for this generator or null */ public final void init(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws KeyManagementException { contextSpi.engineInit(km, tm, random); } \r\n``` \r\n# 4. 详细源码\r\nhttps://github.com/shinerio/java-tutorials#"},{"id":"velocity","title":"Velocity","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"velocity","description":"1. 新建module-a 引入maven依赖 自定义Annotation 继承AbstractProcessor,实现自定义Processor 2. 新建module-b 添加依赖module-A 类填写注解,以生成编译后的class。可以添加些属性,用于编译class时使用。也可以给多个类添加注...","relativePath":"Tech/Code/java/velocity.md","rawContent":"# 1. 新建module-a\r\n引入maven依赖\r\n```xml\r\n \r\n org.apache.velocity \r\n velocity-engine-core \r\n \r\n \r\n com.google.auto.service \r\n auto-service \r\n\r\n```\r\n自定义Annotation\r\n```java\r\n@Target(ElementType.TYPE) \r\n@Retention(RetentionPolicy.SOURCE) \r\npublic @interface CodeGeneratorAnnotation { \r\n}\r\n```\r\n继承AbstractProcessor,实现自定义Processor\r\n```java\r\n@AutoService(Processor.class) \r\n@SupportedAnnotationTypes(\"com.example.CodeGeneratorAnnotation\") \r\npublic class CodeGenerator extends AbstractProcessor {\r\n}\r\n```\r\n# 2. 新建module-b\r\n添加依赖module-A\r\n\r\n类填写注解,以生成编译后的class。`AlarmGenerator`可以添加些属性,用于编译class时使用。也可以给多个类添加注解以生成多份class.\r\n```java\r\n@AlarmGenerator \r\npublic class AlarmGeneratedEntity { \r\n}\r\n```\r\n\r\n>velocity需要使用单独module,这样才能是processor先经过编译,这样再编译其他module的时候才能用上这个已经编译processor"},{"id":"内存管理","title":"内存管理","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":7,"slug":"内存管理","description":"1. 总结 java应用的内存主要分为三块 1. jvm管理的堆内存 2. jvm管理的非堆内存 3. 非jvm管理的内存 1.1. JVM管理的堆内存(Heap Memory) 存储对象实例,是 Java 内存管理的核心区域,由所有线程共享。 - 新生代(Young Generation):存放新...","relativePath":"Tech/Code/java/内存管理.md","rawContent":"# 1. 总结\r\njava应用的内存主要分为三块\r\n1. jvm管理的堆内存\r\n2. jvm管理的非堆内存\r\n3. 非jvm管理的内存\r\n## 1.1. JVM管理的堆内存(Heap Memory)\r\n存储对象实例,是 Java 内存管理的核心区域,由所有线程共享。\r\n- **新生代(Young Generation)**:存放新创建的对象,包括 Eden 区和两个 Survivor 区(S0、S1),对象在此经历多次 GC 后若存活会晋升到老年代。\r\n- **老年代(Old Generation)**:存储生命周期较长的对象,如长期持有的缓存对象。\r\n## 1.2. JVM管理的非堆内存(Non-Heap Memory)\r\n存储与JVM运行时环境相关的数据,不直接存放对象实例。\r\n- **元空间(Metaspace)**:逻辑上仍属堆外内存,但由JVM管理,存放类结构、方法字节码、静态变量等。\r\n- **JVM 栈(JVM Stack)**:每个线程独有,存储栈帧(局部变量、方法参数、操作数栈等),栈深度过大会导致`StackOverflowError`。\r\n- **本地方法栈(Native Method Stack)**:用于调用 Native 方法(如 JVM 底层 C/C++ 代码)的栈空间。\r\n- **程序计数器(Program Counter Register)**:记录当前线程执行的字节码位置,是最小的内存区域。\r\n## 1.3. 非JVM管理的内存(Off-Heap Memory)\r\n由操作系统直接管理,JVM通过本地接口访问,不受JVM堆内存限制。\r\n- **直接内存(Direct Memory)**:通过`java.nio.ByteBuffer.allocateDirect()`创建,用于NIO操作(如文件读写、网络通信),避免堆内存与本地内存的拷贝开销。\r\n- **JNI本地内存**:通过JNI调用C/C++代码分配的内存,需手动释放(如使用malloc,free等),否则可能导致内存泄漏。\r\n- **堆外缓存**:如Redis客户端、Netty框架的内存池,直接使用系统内存提高性能。\r\n> [!warning]\r\n非 JVM 管理的内存不受 GC 控制,若使用不当(如大量分配未释放),可能导致系统 OOM(Out of Memory)\r\n## 1.4. 不同内存的观测方法\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022332395.png)\r\n> [!note]\r\n> 通过Unsafe方法直接管理的内存也可以通过jcmd观测到\r\n# 2. 实验\r\n## 2.1. 测试应用\r\n测试应用可以通过以下命令获取\r\n```shell\r\ngit clone git@github.com:shinerio/java_memory_occupy_analyzer.git\r\n```\r\n可以通过以下命令启动\r\n```shell\r\njava -XX:NativeMemoryTracking=detail -Xms256m -Xmx256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10M -jar demo-0.0.1-SNAPSHOT.jar\r\n```\r\n## 2.2. heap memory\r\n### 2.2.1. 测试命令\r\n```shell\r\ncurl localhost:8080/heap/128\r\ncurl localhost:8080/heap/release\r\n```\r\n### 2.2.2. OOM异常\r\n```\r\njava.lang.OutOfMemoryError: Java heap space\r\n```\r\n### 2.2.3. arthas观测\r\n堆内存正常使用和释放\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022307583.png)\r\n### 2.2.4. jcmd观测\r\n```\r\njcmd `ps -ef|grep demo|grep -v grep|awk '{print $2}'` VM.native_memory scale=MB\r\n```\r\n关键信息如下\r\n```\r\n Total: reserved=894MB, committed=190MB\r\n malloc: 36MB #122322\r\n mmap: reserved=858MB, committed=154MB\r\n\r\n- Java Heap (reserved=256MB, committed=66MB)\r\n (mmap: reserved=256MB, committed=66MB, at peak) \r\n\r\n# curl localhost:8080/heap/128\r\n\r\nTotal: reserved=879MB, committed=299MB\r\n malloc: 21MB #130291\r\n mmap: reserved=858MB, committed=279MB\r\n\r\n- Java Heap (reserved=256MB, committed=186MB)\r\n (mmap: reserved=256MB, committed=186MB, at peak) \r\n\r\n# curl localhost:8080/heap/release\r\n\r\nTotal: reserved=878MB, committed=174MB\r\n malloc: 20MB #128567\r\n mmap: reserved=858MB, committed=154MB\r\n\r\n- Java Heap (reserved=256MB, committed=64MB)\r\n (mmap: reserved=256MB, committed=64MB, peak=237MB) \r\n```\r\n### 2.2.5. rss观测\r\n- `-Xms` 参数用于指定 JVM 启动时**逻辑上分配的堆内存大小**,例如 `-Xms256m` 表示初始堆大小为 256MB。\r\n- **操作系统不会立即为这些内存分配物理页面**(即 RAM 空间),而是在 JVM 需要实际使用内存时(例如对象创建)才逐步分配。\r\nJVM 采用 “延迟分配” 策略:\r\n- 初始时,堆内存仅在逻辑上被预留(通过 `mmap` 系统调用分配地址空间),但物理内存(RSS)不会立即增长。\r\n- 当对象被创建并填充数据时,操作系统才会为这些内存区域分配实际的物理页面,此时 RSS 才会逐渐增加。\r\n> [!note] \r\n一旦堆内存被使用后,即使后面JVM通过GC释放了堆内存,超过xms设定部分的内存也不会还给系统,体现在RSS值不会小于xms。\r\n#### 2.2.5.1. xms < xmx\r\n```\r\n-Xms64m -Xmx256m\r\n```\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022305356.png)\r\n\r\n- 执行`curl localhost:8080/heap/128`分配堆内存\r\n- 执行`curl localhost:8080/heap/release`释放堆内存\r\nGC日志\r\n```\r\n[2025-07-02T23:04:07.843+0800][89.112s][info][gc ] GC(9) Pause Full (System.gc()) 145M->12M(64M) 26.362ms\r\n```\r\n#### 2.2.5.2. xms = xmx\r\n```\r\n-Xms256m -Xmx256m\r\n```\r\n- 执行`curl localhost:8080/heap/128`分配堆内存\r\n- 执行`curl localhost:8080/heap/release`释放堆内存\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022300670.png)\r\nGC日志\r\n```\r\n[2025-07-02T23:01:41.604+0800][147.067s][info][gc ] GC(7) Pause Full (System.gc()) 143M->11M(256M) 25.176ms\r\n```\r\n## 2.3. unsafe\r\n1. 直接使用unsafe命令分配的内存**不受**`-XX:MaxDirectMemorySize`控制。\r\n2. unsafe底层使用malloc进行内存分配,通常是**虚拟内存**,操作系统仅分配虚拟地址空间,而不立即分配物理内存(RAM)。\r\n3. 只有当虚拟内存被**读写**时才会触发**缺页中断**(Page Fault)时,操作系统才会分配物理页并将其加载到RAM中,此时才会计入RSS。\r\n### 2.3.1. OOM\r\n连续执行`curl localhost:8080/unsafe/512`尝试分配内存,当超过系统物理内存上限后,进程会被直接kill\r\n```\r\n2025-07-02T23:08:27.140+08:00 INFO 2181 --- [demo] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'\r\n2025-07-02T23:08:27.140+08:00 INFO 2181 --- [demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'\r\n2025-07-02T23:08:27.141+08:00 INFO 2181 --- [demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms\r\nKilled\r\n```\r\n查看系统日志,可以看到进程直接被kill了\r\n```\r\nJul 2 23:09:16 shinerio-huoshan kernel: oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0-1,global_oom,task_memcg=/user.slice/user-0.slice/session-1.scope,task=java,pid=2181,uid=0\r\nJul 2 23:09:16 shinerio-huoshan kernel: Out of memory: Killed process 2181 (java) total-vm:3353044kB, anon-rss:1445296kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:3164kB oom_score_adj:0\r\nJul 2 23:09:16 shinerio-huoshan systemd[1]: session-1.scope: A process of this unit has been killed by the OOM killer.\r\n```\r\n### 2.3.2. 测试命令\r\n```shell\r\ncurl localhost:8080/unsafe/128\r\ncurl localhost:8080/unsafe/release\r\n```\r\n### 2.3.3. arthas观测\r\nunsafe命令直接分配和释放的内存无法通过arthas观察\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022320627.png)\r\n### 2.3.4. jcmd观测\r\n分配前(无other)\r\n```\r\n- Java Heap (reserved=256MB, committed=256MB)\r\n (mmap: reserved=256MB, committed=256MB, at peak) \r\n```\r\n分配后\r\n```\r\n- Java Heap (reserved=256MB, committed=256MB)\r\n (mmap: reserved=256MB, committed=256MB, at peak)\r\n\r\n- Other (reserved=128MB, committed=128MB)\r\n (malloc=128MB #5) (at peak) \r\n```\r\n释放后\r\n```\r\n- Java Heap (reserved=256MB, committed=256MB)\r\n (mmap: reserved=256MB, committed=256MB, at peak) \r\n \r\n- Other (reserved=0MB, committed=0MB)\r\n (malloc=0MB #7) (peak=128MB #6) \r\n```\r\n### 2.3.5. rss观测\r\n直接使用unsafe命令分配和释放的内存可以通过jcmd观测\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022325168.png)\r\n### 2.3.6. pmap观测\r\n输出内存显示为`anon`\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507032332888.png)\r\n## 2.4. ByteBuffer.allocateDirect\r\n通过`ByteBuffer.allocateDirect`分配和释放的内存会立即体现在RSS的变化上,其内部通过`UNSAFE`分配内存后,立即调用了`UNSAFE.setMemory(base, size, (byte) 0);`方法,触发了缺页中断,实际分配了物理内存。\r\n### 2.4.1. 命令\r\n```shell\r\n# 分配128M直接内存\r\ncurl localhost:8080/direct_byte_buffer/128\r\n# 释放\r\ncurl localhost:8080/direct_byte_buffer/release\r\n```\r\n### 2.4.2. OOM\r\n直接内存的大小默认与`-xmx`设定相当,可通过以下命令显示指定\r\n```bash\r\n-XX:MaxDirectMemorySize=128m\r\n```\r\n超过上限后会oom\r\n```\r\njava.lang.OutOfMemoryError: Cannot reserve 134217728 bytes of direct buffer memory (allocated: 134252544, limit: 268435456)\r\n```\r\n### 2.4.3. arthas观测\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022238405.png)\r\n### 2.4.4. jcmd观测\r\n分配前\r\n```\r\n- Java Heap (reserved=256MB, committed=64MB)\r\n (mmap: reserved=256MB, committed=64MB, peak=66MB) \r\n \r\n- Compiler (reserved=0MB, committed=0MB)\r\n (arena=0MB #4) (peak=24MB #11)\r\n \r\n- Internal (reserved=2MB, committed=2MB)\r\n (malloc=2MB #6615) (at peak) \r\n```\r\n分配后\r\n```\r\n- Java Heap (reserved=256MB, committed=64MB)\r\n (mmap: reserved=256MB, committed=64MB, peak=66MB) \r\n \r\n- Internal (reserved=2MB, committed=2MB)\r\n (malloc=2MB #6627) (peak=2MB #6615) \r\n \r\n- Other (reserved=132MB, committed=132MB)\r\n (malloc=132MB #24) (at peak) \r\n```\r\n释放后\r\n```\r\n- Java Heap (reserved=256MB, committed=64MB)\r\n (mmap: reserved=256MB, committed=64MB, peak=66MB) \r\n \r\n- Internal (reserved=3MB, committed=3MB)\r\n (malloc=3MB #6812) (peak=3MB #6804) \r\n \r\n- Other (reserved=4MB, committed=4MB)\r\n (malloc=4MB #26) (peak=132MB #26) \r\n```\r\n### 2.4.5. rss观测\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022243125.png)\r\n## 2.5. netty(PooledByteBufAllocator)\r\n### 2.5.1. 命令\r\n```shell\r\ncurl localhost:8080/netty/128\r\ncurl localhost:8080/netty/release\r\n```\r\n### 2.5.2. oom\r\nnetty使用的也是直接内存,也受`-XX:MaxDirectMemorySize`参数控制,超过后会oom。`-[Dio.netty.maxDirectMemory](Dio.netty.maxDirectMemory)Dio.netty.maxDirectMemoryDio.netty.maxDirectMemory`并不会限制用例中PooledByteBufAllocator的内存分配。\r\n```\r\njava.lang.OutOfMemoryError: Cannot reserve 536870912 bytes of direct buffer memory (allocated: 4237314, limit: 268435456)\r\n```\r\n### 2.5.3. arthas观测\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507032250775.png)\r\n### 2.5.4. jcmd观测\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507032253844.png)\r\n### 2.5.5. rss观测\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507032255387.png)\r\n\r\n## 2.6. JNI\r\n### 2.6.1. 启动\r\n```shell\r\njava -XX:NativeMemoryTracking=detail -Xms256m -Xmx256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10M -Djava.library.path=$PROJECT_ROOT/src/main/native -jar demo-0.0.1-SNAPSHOT.jar\r\n```\r\n### 2.6.2. 测试命令\r\n```shell\r\ncurl localhost:8080/jni/128\r\ncurl localhost:8080/jni/release\r\n```\r\n### 2.6.3. oom\r\n执行`curl localhost:8080/jni/1280`分配超大内存\r\n```\r\n# 应用被系统自动kill\r\n2025-07-03T22:18:00.323+08:00 INFO 23454 --- [demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms\r\nKilled\r\n\r\n# 系统日志显示oom并自动kill进程\r\nJul 3 22:18:12 shinerio-huoshan kernel: oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=tuned.service,mems_allowed=0-1,global_oom,task_memcg=/user.slice/user-0.slice/session-7.scope,task=java,pid=23454,uid=0\r\nJul 3 22:18:12 shinerio-huoshan kernel: Out of memory: Killed process 23454 (java) total-vm:3615188kB, anon-rss:1458780kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:3180kB oom_score_adj:0\r\nJul 3 22:18:12 shinerio-huoshan systemd[1]: session-7.scope: A process of this unit has been killed by the OOM killer.\r\n```\r\n### 2.6.4. arthas观测\r\njarthas无法观察到通过`jni`直接调用`malloc`分配的内存,其绕过了jvm内部的分配器。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507032232844.png)\r\n### 2.6.5. jcmd观测\r\narthas无法观察到通过`jni`直接调用`malloc`分配的内存\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507032242304.png)\r\n### 2.6.6. rss观测\r\n通过`jni`直接调用`malloc`分配和释放的内存会体现在系统rss指标上\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507032243038.png)\r\n### 2.6.7. pmap观测\r\n```shell\r\npmap -x `ps -ef|grep demo|grep -v grep|awk '{print $2}'` | sort -nrk3\r\n```\r\n1. Linux将进程内存虚拟为伪文件/proc/$pid/mem,通过它即可查看进程内存中的数据。\r\n2. tail用于偏移到指定内存段的起始地址,即pmap的第一列,head用于读取指定大小,即pmap的第二列。\r\n3. strings用于找出内存中的字符串数据,less用于查看strings输出的字符串。如果内存中不是字符串,也可以不加strings原样输出\r\n```shell\r\ntail -c +$((0x00007face0000000+1)) /proc/`ps -ef|grep demo|grep -v grep|awk '{print $2}'`/mem|head -c $((11616*1024))|strings|less -S\r\n```\r\n通过jni分配的内存显示为`anno`\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507032314044.png)\r\n\r\n## 2.7. 综合实验\r\n启动命令\r\n```shell\r\njava -XX:NativeMemoryTracking=detail -Xms256m -Xmx256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10M -Djava.library.path=$PROJECT_ROOT/src/main/native -jar demo-0.0.1-SNAPSHOT.jar\r\n```\r\n内存占用测试命令\r\n```shell\r\ncurl localhost:8080/heap/128\r\ncurl localhost:8080/heap/64\r\ncurl localhost:8080/heap/32\r\ncurl localhost:8080/heap/16\r\ncurl localhost:8080/heap/8\r\ncurl localhost:8080/heap/4\r\ncurl localhost:8080/heap/2\r\ncurl localhost:8080/heap/1\r\ncurl localhost:8080/heap/1\r\ncurl localhost:8080/netty/128\r\ncurl localhost:8080/unsafe/128\r\ncurl localhost:8080/jni/128\r\n```\r\n### 2.7.1. jcmd详细统计报表\r\n\r\n| 分类 | Reserved | Committed | 说明 |\r\n| ---------------------- | -------- | --------- | ------------------------------------------------------- |\r\n| **Java Heap** | 256 | 256 | Java 堆内存,由JVM控制,通常通过 `mmap` 分配,这里堆内存几乎占满 |\r\n| **Class** | 209 | 5 | 包括类元数据(Metadata)和类空间(Class space),用于加载类结构信息。 |\r\n| ├─ Metadata | 64 | 28 | 类的结构定义,如字段、方法、注解信息等。 |\r\n| └─ Class space | 208 | 4 | 用于存储类的静态信息等;Java 8+中取代永久代的一部分。 |\r\n| **Thread** | 35 | 3 | 每个线程的本地栈空间,默认每线程约 1MB。 |\r\n| **Code** | 243 | 12 | JIT编译后的代码缓存区,如 CodeCache。 |\r\n| **GC** | 42 | 42 | 垃圾回收器的工作内存,如标记、扫描、复制等临时结构。 |\r\n| **Compiler** | 0 | 0 | JIT 编译器本身使用的内存(如优化数据结构),当前无占用。 |\r\n| **Other** | 256 | 256 | 非堆内内存,如DirectByteBuffer和Unsafe分配的本地内存。 |\r\n| **Symbol** | 11 | 11 | 常量池中的符号表、方法名、字段名等字符串。 |\r\n| **NMT Tracking** | 2 | 2 | Native Memory Tracking 自身运行时记录用的空间。 |\r\n| **Shared class space** | 16 | 13 | 类数据共享(CDS)机制使用的共享空间。 |\r\n| **Arena Chunk** | 0 | 0 | 本地内存 arena 分配器使用的临时分配区,当前为空。 |\r\n| **Metaspace** | 64 | 28 | JVM 8+ 用于存储类定义元数据,支持动态扩展。 |\r\n| **合计** | **1135** | **629** | `malloc` 总共占用 277MB,`mmap` 保留 858MB,提交 352MB(虚拟保留与实际提交) |\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507042258770.png)\r\n### 2.7.2. 总内存分析\r\nRSS占用为797472 KB ≈ 779MB\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507042311823.png)\r\n`JVM管理内存629 + JNI分配内存128 = 757MB`,剩余22MB可能来自于Linux 分配的线程栈、page tables、管理结构等。\r\n## 2.8. Bits类\r\n`Bits.reserveMemory`和`Bits.unreserveMemory`会更新JVM内部的直接内存计数器,Arthas可以通过读取这些计数器来显示直接内存使用情况。\r\n使用Unsafe方法直接分配和释放的内存,没有经过Bits管理,因此arthas无法观测到。\r\n```java\r\nBits.reserveMemory(size, cap);\r\nBits.unreserveMemory(size, cap);\r\n```\r\n## 2.9. DirectByteBuffer\r\n非jvm管理的内存典型代表为`DirectByteBuffer`,jvm gc只能回收`DirectByteBuffer`对象本身,而无法管理其内部通过Unsafe申请的内存。当`DirectByteBuffer`对象被GC回收时,JVM会通过`Cleaner`机制调用本地方法。\r\n\r\n具体来说,Cleaner类继承自`PhantomReference`,实现了clean方法。JVM启动时会创建一个名为`Reference Handler`的守护线程,其优先级为`MAX_PRIORITY`(10),该线程不断从`ReferenceQueue`中取出引用对象,并调用其clean方法。\r\n```java\r\npublic class Cleaner extends PhantomReference\r\n```\r\n> [!note]\r\n> JVM垃圾回收没有直接回收DBB对象通过Unsafe方法分配的内存,而是通过其Cleaner机制实现了间接释放。内存的分配和释放都是由系统malloc()和free()函数实现的。\r\n## 2.10. metaspace和class space\r\nMetaspace区域位于堆外,最大内存大小取决于系统内存,而不是堆大小,可以通过指定 `MaxMetaspaceSize`参数来限制最大内存。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022031191.png)\r\n虽然每个Java类都关联了一个`java.lang.Class`的实例,而且它是一个贮存在堆中的 Java 对象。但是类的class metadata不是一个Java对象,它不在堆中,而是在 Metaspace 中。\r\n\r\n有两个核心配置参数:\r\n- `-XX:MaxMetaspaceSize`:Metaspace 总空间的最大允许使用内存,默认是不限制。\r\n- `-XX:CompressedClassSpaceSize`:Metaspace中的Compressed Class Space的最大允许内存,默认值是1G,这部分会在JVM启动的时候向操作系统申请1G的**虚拟地址映射**,但不是真的就用了操作系统的1G内存。\r\n### 2.10.1. 分配\r\n当一个类被加载时,它的类加载器会负责在Metaspace中分配空间用于存放这个类的元数据。如下图,可以看到在`Id`这个类加载器第一次加载类`X` 和 `Y` 的时候,在 Metaspace 中为它们开辟空间存放元信息。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022018605.png)\r\n### 2.10.2. 回收\r\n分配给一个类的空间,是归属于这个类的类加载器的,只有当这个类加载器卸载的时候,这个空间才会被释放。所以,只有当这个类加载器加载的所有类都没有存活的对象,并且没有到达这些类和类加载器的引用时,相应的Metaspace空间才会被GC释放。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202507022019804.png)\r\n\r\n### 2.10.3. 系统内存回收\r\n释放Metaspace的空间,并不意味着将这部分空间还给系统内存,这部分空间通常会被JVM保留下来。\r\n\r\n这部分被保留的空间有多大,取决于Metaspace的碎片化程度。另外,Metaspace中有一部分区域Compressed Class Space是一定不会还给操作系统的。\r\n# 3. ref\r\n1. [深入理解堆外内存 Metaspace](https://www.javadoop.com/post/metaspace)\r\n2. [Understanding Metaspace and Class Space GC Log Entries](https://poonamparhar.github.io/understanding-metaspace-gc-logs/)\r\n3. [一次Java内存占用高的排查案例,解释了我对内存问题的所有疑问](https://www.cnblogs.com/codelogs/p/17659370.html \"发布于 2023-08-26 19:46\")"},{"id":"虚拟线程","title":"虚拟线程","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":13,"slug":"虚拟线程","description":"1. 概念 1.1. 平台线程 我们常用的Java线程与系统内核线程是一一对应的,系统内核的线程调度程序负责调度 Java线程。为了增加应用程序的性能,我们会增加越来越多的Java线程,而由于多种因素,平台线程的数量受到了很大的约束。 - 资源有限导致系统线程总量有限,进而导致与系统线程一一对应的平...","relativePath":"Tech/Code/java/虚拟线程.md","rawContent":"# 1. 概念\r\n## 1.1. 平台线程\r\n我们常用的Java线程与系统内核线程是一一对应的,系统内核的线程调度程序负责调度 Java线程。为了增加应用程序的性能,我们会增加越来越多的Java线程,而由于多种因素,平台线程的数量受到了很大的约束。\r\n- 资源有限导致系统线程总量有限,进而导致与系统线程一一对应的平台线程有限。\r\n- 平台线程的调度依赖于系统的线程调度程序,当平台线程创建过多,会消耗大量资源用于处理线程上下文切换。\r\n- 每个平台线程都会开辟一块私有的栈空间,大量平台线程会占据大量内存,每个平台线程需要占用约1MB左右的内存。\r\n这些限制导致开发者不能极大量地创建平台线程,为了满足性能需要,需要引入池化技术、添加任务队列构建消费者-生产者模式等方案去让平台线程适配多变的现实场景。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506132252117.png)\r\n## 1.2. 虚拟线程\r\n虚拟线程(Virtual Thread)是JDK而不是OS实现的轻量级线程(Lightweight Process,LWP),由JVM调度。许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。\r\n- 可以大量创建,例如十万级别、百万级别,而不会占据大量内存\r\n- 由`JVM`进行调度和状态切换,并且与系统线程\"松绑\"\r\n- 用法与原来平台线程差不多,或者说尽量兼容平台线程现存的`API`\r\nJVM 调度程序通过平台线程(载体线程)来管理虚拟线程,一个平台线程可以在不同的时间执行不同的虚拟线程(多个虚拟线程挂载在一个平台线程上),当虚拟线程被阻塞或等待时,平台线程可以切换到执行另一个虚拟线程。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian%E5%B9%B3%E5%8F%B0%E7%BA%BF%E7%A8%8B.png)\r\n相比较于平台线程来说,虚拟线程是廉价且轻量级的,使用完后立即被销毁,因此它们不需要被重用或池化,每个任务可以有自己专属的虚拟线程来运行。虚拟线程暂停和恢复来实现线程之间的切换,避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂,可以有效减少编写、维护和观察高吞吐量并发应用程序的工作量。\r\n# 2. 虚拟线程调度\r\n基于操作系统线程实现的平台线程,JDK依赖于操作系统中的线程调度程序来进行调度。而对于虚拟线程,JDK有自己的调度器。JDK的调度器没有直接将虚拟线程分配给系统线程,而是将虚拟线程分配给平台线程(类似于go语言中G-M-P模型,虚拟线程和平台线程是M:N调度)。JDK 的虚拟线程调度器是一个在FIFO模式下运行的类似`ForkJoinPool`的线程池。调度器的并行数量取决于调度器虚拟线程的平台线程数量。默认情况下是CPU可用核心数量,但可以使用系统属性`jdk.virtualThreadScheduler.parallelism`进行调整。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506141253036.png)\r\n\r\nJDK 的虚拟线程调度器是一个在 FIFO 模式下运行的类似`ForkJoinPool`的线程池。调度器的并行数量取决于调度器虚拟线程的平台线程数量。默认情况下是 CPU 可用核心数量,但可以使用系统属性`jdk.virtualThreadScheduler.parallelism`进行调整。注意,这里的`ForkJoinPool`与`ForkJoinPool.commonPool()`不同,`ForkJoinPool.commonPool()`用于实现并行流,并在 LIFO 模式下运行。\r\n\r\n`ForkJoinPool`和`ExecutorService`的工作方式不同,`ExecutorService`有一个等待队列来存储它的任务,其中的线程将接收并处理这些任务。而`ForkJoinPool`的每一个线程都有一个等待队列,当一个由线程运行的任务生成另一个任务时,该任务被添加到该线程的等待队列中,当我们运行`Parallel Stream`,一个大任务划分成两个小任务时就会发生这种情况。\r\n\r\n为了防止**线程饥饿**问题,当一个线程的等待队列中没有更多的任务时,`ForkJoinPool`还实现了另一种模式,称为**任务窃取**, 也就是说:饥饿线程可以从另一个线程的等待队列中窃取一些任务。这和 Go G-M-P 模型中 work stealing 机制有异曲同工之妙。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506141250621.png)\r\n# 3. 虚拟线程执行\r\n当虚拟线程执行I/O或JDK中的其他阻止操作(如`BlockingQueue.take()`时,虚拟线程会从平台线程上卸载。当阻塞操作准备完成时(例如,网络 IO 已收到字节数据),调度程序将虚拟线程挂载到平台线程上以恢复执行。\r\n\r\nJDK中的绝大多数阻塞操作会将虚拟线程从平台线程上卸载,使平台线程能够执行其他工作任务。但是,JDK中的少数阻塞操作不会卸载虚拟线程,因此会阻塞平台线程。因为操作系统级别(例如许多文件系统操作)或 JDK级别(例如`Object.wait()`)的限制。这些阻塞操作阻塞平台线程时,将通过暂时增加平台线程的数量来补偿其他平台线程阻塞的损失。因此,调度器的`ForkJoinPool`中的平台线程数量可能会暂时超过CPU可用核心数量。调度器可用的平台线程的最大数量可以使用系统属性`jdk.virtualThreadScheduler.maxPoolSize`进行调整。这个阻塞补偿机制与Go G-M-P模型中hand off机制有异曲同工之妙。\r\n\r\n在以下两种情况下,虚拟线程会被固定到运行它的平台线程,在阻塞操作期间无法卸载虚拟线程:\r\n1. 当在`synchronized`块或方法中执行代码时。\r\n2. 当执行`native`方法或foreign function时。\r\n虚拟线程被固定不会影响程序运行的正确性,但它可能会影响系统的并发度和吞吐量。如果虚拟线程**在被固定时**执行I/O或`BlockingQueue.take()` 等阻塞操作,则负责运行它的平台线程在操作期间会被阻塞。如果虚拟线程**没有被固定**,那会执行I/O等阻塞操作时会从平台线程上卸载。\r\n# 4. 优缺点\r\n## 4.1. 优点\r\n- **非常轻量级**:可以在单个线程中创建成百上千个虚拟线程而不会导致过多的线程创建和上下文切换。\r\n- **简化异步编程**: 虚拟线程可以简化异步编程,使代码更易于理解和维护。它可以将异步代码编写得更像同步代码,避免了回调地狱(Callback Hell)。\r\n- **减少资源开销**: 由于虚拟线程是由 JVM 实现的,它能够更高效地利用底层资源,例如 CPU 和内存。虚拟线程的上下文切换比平台线程更轻量,因此能够更好地支持高并发场景。\r\n## 4.2. 缺点\r\n- **不适用于计算密集型任务**: 虚拟线程适用于I/O密集型任务,但不适用于计算密集型任务,因为密集型计算始终需要CPU资源作为支持。\r\n- **与某些第三方库不兼容**: 虽然虚拟线程设计时考虑了与现有代码的兼容性,但某些依赖平台线程特性的第三方库可能不完全兼容虚拟线程。\r\n# 5. 创建虚拟线程\r\n```java\r\nThread thread1 = Thread.ofVirtual().start(() -> { \r\n try { \r\n TimeUnit.SECONDS.sleep(1); \r\n } catch (InterruptedException ignored) { \r\n } \r\n System.out.println(\"Virtual Thread 1 is running\"); \r\n}); \r\n \r\nThread thread2 = Thread.startVirtualThread(() -> { \r\n try { \r\n TimeUnit.SECONDS.sleep(2); \r\n } catch (InterruptedException ignored) { \r\n } \r\n System.out.println(\"Virtual Thread 2 is running\"); \r\n}); \r\n \r\ntry (var virtualThreadPerTaskExecutor = Executors.newVirtualThreadPerTaskExecutor()) { \r\n virtualThreadPerTaskExecutor.submit(() -> { \r\n try { \r\n TimeUnit.SECONDS.sleep(3); \r\n } catch (InterruptedException ignored) { \r\n } \r\n System.out.println(\"Virtual Thread 3 is running\"); \r\n }); \r\n} \r\n \r\nThreadFactory factory = Thread.ofVirtual().factory(); \r\nThread thread4 = factory.newThread(() -> { \r\n try { \r\n TimeUnit.SECONDS.sleep(4); \r\n } catch (InterruptedException ignored) { \r\n } \r\n System.out.println(\"Virtual Thread 4 is running\"); \r\n}); \r\nthread4.start(); \r\n \r\nthread1.join(); \r\nthread2.join(); \r\nthread4.join();\r\n```\r\n# 6. 虚拟线程卸载实验\r\n## 6.1. sleep卸载\r\n```java\r\nvar threads = IntStream.range(0, 5).mapToObj(index -> Thread.ofVirtual().unstarted(() -> { \r\n System.out.println(Thread.currentThread()); \r\n try { \r\n Thread.sleep(10); \r\n } catch (InterruptedException e) { \r\n throw new RuntimeException(e); \r\n } \r\n System.out.println(Thread.currentThread()); \r\n})).toList(); \r\n \r\nthreads.forEach(Thread::start); \r\nfor (Thread thread : threads) { \r\n thread.join(); \r\n}\r\n```\r\n可以看到虚拟线程在sleep前后都切换了实际工作的平台线程\r\n```text\r\nVirtualThread[#32]/runnable@ForkJoinPool-1-worker-1\r\nVirtualThread[#34]/runnable@ForkJoinPool-1-worker-3\r\nVirtualThread[#33]/runnable@ForkJoinPool-1-worker-2\r\nVirtualThread[#35]/runnable@ForkJoinPool-1-worker-4\r\nVirtualThread[#36]/runnable@ForkJoinPool-1-worker-5\r\nVirtualThread[#34]/runnable@ForkJoinPool-1-worker-4\r\nVirtualThread[#35]/runnable@ForkJoinPool-1-worker-8\r\nVirtualThread[#32]/runnable@ForkJoinPool-1-worker-1\r\nVirtualThread[#36]/runnable@ForkJoinPool-1-worker-7\r\nVirtualThread[#33]/runnable@ForkJoinPool-1-worker-7\r\n```\r\n虚拟线程 sleep 时真正调用的方法是 `Continuation.yield`,会将当前虚拟线程的堆栈由平台线程的堆栈转移到Java堆内存,然后将其他就绪虚拟线程的堆栈由Java堆中拷贝到当前平台线程的堆栈中继续执行。\r\n> [!note]\r\n> 执行 IO 或`BlockingQueue.take()` 等阻塞操作时会跟 sleep 一样导致虚拟线程切换。虚拟线程的切换也是一个相对耗时的操作,但是与平台线程的上下文切换相比,还是轻量很多的\r\n\r\n在synchronized代码块中运行的虚拟线程不会发生卸载\r\n```java\r\nvar threads = IntStream.range(0, 5).mapToObj(index -> Thread.ofVirtual().unstarted(() -> { \r\n synchronized (JDK21FeatureTest.class) { \r\n System.out.println(Thread.currentThread()); \r\n try { \r\n Thread.sleep(10); \r\n } catch (InterruptedException e) { \r\n throw new RuntimeException(e); \r\n } \r\n System.out.println(Thread.currentThread()); \r\n } \r\n})).toList(); \r\n \r\nthreads.forEach(Thread::start); \r\nfor (Thread thread : threads) { \r\n thread.join(); \r\n}\r\n```\r\n执行结果\r\n```text\r\nVirtualThread[#35]/runnable@ForkJoinPool-1-worker-3\r\nVirtualThread[#35]/runnable@ForkJoinPool-1-worker-3\r\nVirtualThread[#37]/runnable@ForkJoinPool-1-worker-5\r\nVirtualThread[#37]/runnable@ForkJoinPool-1-worker-5\r\nVirtualThread[#36]/runnable@ForkJoinPool-1-worker-4\r\nVirtualThread[#36]/runnable@ForkJoinPool-1-worker-4\r\nVirtualThread[#34]/runnable@ForkJoinPool-1-worker-2\r\nVirtualThread[#34]/runnable@ForkJoinPool-1-worker-2\r\nVirtualThread[#33]/runnable@ForkJoinPool-1-worker-1\r\nVirtualThread[#33]/runnable@ForkJoinPool-1-worker-1\r\n```\r\n`synchronized`代码块借助内置锁(监视器锁)来保证线程安全。当一个线程进入`synchronized`代码块时,它会获取对应的锁,要是锁被其他线程占用,该线程就会被**阻塞**。这种阻塞是由操作系统内核来处理的,会使线程进入等待状态,直到锁被释放。可以使用ReentrantLock替代。\r\n## 6.2. IO卸载\r\n```java\r\nvar threads = IntStream.range(0, 5).mapToObj(index -> Thread.ofVirtual().unstarted(() -> { \r\n System.out.println(Thread.currentThread()); \r\n try { \r\n HttpRequest request = HttpRequest.newBuilder() \r\n .uri(URI.create(\"https://localhost:5000/account\")) \r\n .GET() \r\n .build(); \r\n MTLSHttpClientTest.getHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); \r\n } catch (Exception ignored) { \r\n } \r\n System.out.println(Thread.currentThread()); \r\n})).toList(); \r\n \r\nthreads.forEach(Thread::start); \r\nfor (Thread thread : threads) { \r\n thread.join(); \r\n}\r\n```\r\n执行结果\r\n```text\r\nVirtualThread[#35]/runnable@ForkJoinPool-1-worker-2\r\nVirtualThread[#34]/runnable@ForkJoinPool-1-worker-1\r\nVirtualThread[#36]/runnable@ForkJoinPool-1-worker-3\r\nVirtualThread[#37]/runnable@ForkJoinPool-1-worker-4\r\nVirtualThread[#38]/runnable@ForkJoinPool-1-worker-4\r\nVirtualThread[#37]/runnable@ForkJoinPool-1-worker-2\r\nVirtualThread[#34]/runnable@ForkJoinPool-1-worker-3\r\nVirtualThread[#38]/runnable@ForkJoinPool-1-worker-2\r\nVirtualThread[#36]/runnable@ForkJoinPool-1-worker-10\r\nVirtualThread[#35]/runnable@ForkJoinPool-1-worker-11\r\n```\r\n\r\n# 7. 性能测试\r\n## 7.1. IO密集型\r\n应用程序符合下面两点特征,使用虚拟线程可以显著提高程序吞吐量:\r\n- 程序并发任务数量很高。\r\n- IO密集型、工作负载不受 CPU 约束。\r\n虚拟线程有助于提高服务端应用程序的吞吐量,因为此类应用程序大部分都是CRUD业务,且有大量并发,而且这些任务通常会有大量的IO等待。\r\n```java\r\n@Test \r\npublic void testPerformance() { \r\n AtomicInteger threadNum = new AtomicInteger(0); \r\n // 开启线程 统计平台线程数 \r\n ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); \r\n scheduledExecutorService.scheduleAtFixedRate(() -> { \r\n ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); \r\n ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false); \r\n if (threadInfo.length > threadNum.get()) { \r\n threadNum.set(threadInfo.length); \r\n } \r\n }, 10, 10, TimeUnit.MILLISECONDS); \r\n \r\n long start = System.currentTimeMillis(); \r\n // 限制虚拟线程使用的平台线程数量\r\n // System.setProperty(\"jdk.virtualThreadScheduler.parallelism\", \"4\");\r\n // 虚拟线程 \r\n // ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); \r\n // 使用平台线程 \r\n ExecutorService executor = Executors.newFixedThreadPool(1000); \r\n for (int i = 0; i < 10000; i++) { \r\n executor.submit(() -> { \r\n try { \r\n // 线程睡眠 0.5 s,模拟业务处理 \r\n TimeUnit.MILLISECONDS.sleep(500); \r\n } catch (InterruptedException ignored) { \r\n } \r\n }); \r\n } \r\n executor.close(); \r\n System.out.printf(\"totalMillis:%dms\\tmax platform thread/os thread num: %d\\n\", System.currentTimeMillis() - start, threadNum.get()); \r\n}\r\n```\r\n虚拟线程测试结果\r\n- 不同平台线程数量对结果影响不大\r\n```txt\r\n# 限制虚拟线程使用的平台线程数量\r\n# System.setProperty(\"jdk.virtualThreadScheduler.parallelism\", \"4\"); \r\n# ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();\r\ntotalMillis:850ms\tmax platform thread/os thread num: 19\r\n\r\ntotalMillis:980ms\tmax platform thread/os thread num: 27\r\n```\r\n平台线程测试结果\r\n```txt\r\ntotalMillis:25362ms max platform thread/os thread num: 214\r\n\r\ntotalMillis:5247ms max platform thread/os thread num: 1014\r\n\r\ntotalMillis:2895ms max platform thread/os thread num: 2014\r\n\r\ntotalMillis:8038ms max platform thread/os thread num: 9895\r\n```\r\n## 7.2. 计算密集型\r\n将上述代码替换成cpu密集型的指数运算,可以发现计算密集型任务使用和CPU核心数2倍左右的平台线程数会有更好的效果。如果我们限制虚拟线程使用的平台线程数量,会看到任务处理时间大大增加,说明计算密集型任务还是依赖底层平台线程。\r\n> 如果平台线程数增加,可能会加剧线程上下文切换,反而适得其反。\r\n```java\r\nfor (int i = 0; i < 10000; i++) { \r\n executor.submit(() -> { \r\n Random random = new Random(); \r\n for (int j = 0; j < 100000; j++) { \r\n // 模拟一些工作 \r\n Math.pow(random.nextInt(100), random.nextInt(10)); \r\n } \r\n }); \r\n}\r\n```\r\n虚拟线程测试结果\r\n- 不同平台线程数量对测试结果影响很大\r\n```text\r\n# 限制虚拟线程使用的平台线程数量\r\n# System.setProperty(\"jdk.virtualThreadScheduler.parallelism\", \"4\"); \r\n# ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();\r\ntotalMillis:23099ms\tmax platform thread/os thread num: 19\r\n\r\ntotalMillis:3657ms\tmax platform thread/os thread num: 26\r\n```\r\n平台线程测试结果\r\n```text\r\n# ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());\r\ntotalMillis:3703ms\tmax platform thread/os thread num: 26\r\n\r\n# ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);\r\ntotalMillis:3147ms\tmax platform thread/os thread num: 38\r\n\r\ntotalMillis:3138ms\tmax platform thread/os thread num: 1014\r\n\r\ntotalMillis:9340ms\tmax platform thread/os thread num: 9404\r\n```\r\n# 8. ref\r\nhttps://zhuanlan.zhihu.com/p/669327999\r\nhttps://www.zhihu.com/question/536743167\r\nhttps://javaguide.cn/java/new-features/java19.html"},{"id":"grpc","title":"Grpc","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"grpc","description":"1. 工具推荐 - apifox:rpc客户端,可以导入proto文件,自动生成rpc客户端并重试 2. 通信过程 3. 抓包 4. protocol buffers Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数...","relativePath":"Tech/Code/rpc/grpc.md","rawContent":"# 1. 工具推荐\r\n- apifox:rpc客户端,可以导入proto文件,自动生成rpc客户端并重试\r\n# 2. 通信过程\r\n![grpc_protocol_diagram.svg](https://shinerio.oss-cn-beijing.aliyuncs.com/grpc_protocol_diagram.svg)\r\n# 3. 抓包\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/20250725232203407.png)\r\n# 4. protocol buffers\r\nProtobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数据存储格式。\r\n## 4.1. Varints编码\r\n 普通的int数据类型,无论其值的大小,所占用的存储空间都是相等的。⽐如不管是0x12345678还是0x12都占⽤4字节,那能否让0x12在表示的时候只占⽤1个字节呢?是否可以根据数值的⼤⼩来动态地占⽤存储空间, 使得值⽐较⼩的数字占⽤较少的字节数, 值相对⽐较⼤的数字占⽤较多的字节数, 这即是变⻓整型编码的基本思想。\r\n \r\n Varints编码采⽤变⻓整型编码的数字,其占⽤的字节数不是完全⼀致的\r\n - 每个字节的最⾼有效位(Most Significant Bit, MSB)作为标志位,当最⾼有效位为1时,代表其后还跟有字节,当最⾼有效位为0时,代表已经是该数字的最后的⼀个字节\r\n - 剩余的7位以⼆进制补码的形式来存储数字值本身\r\n\r\n在 Protobuf 中, 使⽤的是Base128 Varints编码, 在这种⽅式中, 使⽤7bit (即7的2次⽅为128) 来存储数字,在 Protobuf中, Base128 Varints采⽤的是⼩端序(即数字的低位存放在⾼地址)。 \r\n\r\n1. 对于数字1,假设int类型占4个字节, 以标准的整型存储, 其⼆进制表示应为:\r\n```text\r\n00000000 00000000 00000000 00000001\r\n```\r\n可⻅,只有最后⼀个字节存储了有效数值, 前3个字节都是 0, 若采⽤Varints编码, 其⼆进制形式为:\r\n```text\r\n00000001\r\n```\r\n因为其没有后续字节,因此其最⾼有效位为0,其余的7位以补码形式存放1。\r\n# 5. .proto文件\r\nporto文件在客户端和服务器端是共用的\r\n```conf\r\nsyntax = \"proto3\";\r\n\r\npackage sum;\r\n\r\n// 导入其他proto文件 \r\nimport \"google/protobuf/timestamp.proto\"; \r\nimport \"google/protobuf/any.proto\";\r\n\r\n// SumService provides arithmetic operations\r\nservice SumService {\r\n // AddNumbers adds two integers and returns the result\r\n rpc AddNumbers(AddRequest) returns (AddResponse);\r\n \r\n // SubtractNumbers subtracts num2 from num1 and returns the result\r\n rpc SubtractNumbers(SubtractRequest) returns (SubtractResponse);\r\n}\r\n\r\n// AddRequest contains the two numbers to be added\r\nmessage AddRequest {\r\n int32 num1 = 1; // First number\r\n int32 num2 = 2; // Second number\r\n}\r\n\r\n// AddResponse contains the result of the addition\r\nmessage AddResponse {\r\n int32 result = 1; // Sum of num1 and num2\r\n}\r\n\r\n// SubtractRequest contains the two numbers for subtraction\r\nmessage SubtractRequest {\r\n int32 num1 = 1; // First number (minuend)\r\n int32 num2 = 2; // Second number (subtrahend)\r\n}\r\n\r\n// SubtractResponse contains the result of the subtraction\r\nmessage SubtractResponse {\r\n int32 result = 1; // Difference of num1 - num2\r\n}\r\n```\r\n## 5.1. syntax\r\n指定了使用`proto3`语法。如果省略,protocol buffer编译器默认使用 `proto2`语法\r\n## 5.2. package\r\n`.proto`文件中添加一个可选的`package`符来防止消息类型之前的名称冲突。\r\n## 5.3. import\r\n可以导入其他proto文件定义\r\n## 5.4. service\r\n一个package下可以有多个service,每个service可以有多个method,method定义的格式为`rpv methodName(RequestModel) returns ResponseModel`\r\n## 5.5. message\r\n基本格式为`message MessageName { type field_name = field_number; }`\r\n字段编号规则:\r\n- 必须是正整数\r\n- 在同一个message中必须唯一\r\n- 1-15: 使用1字节编码(推荐用于常用字段)\r\n- 16-2047: 使用2字节编码\r\n- 最大值: 2^29 - 1 (536,870,911)\r\n```\r\nmessage FieldNumberExample {\r\n // 定义嵌套的message\r\n message Address {\r\n string street = 1;\r\n string city = 2;\r\n string state = 3;\r\n string zip_code = 4;\r\n string country = 5;\r\n }\r\n \r\n string frequent_field = 1; // 推荐:常用字段使用1-15\r\n string another_field = 2;\r\n string less_used_field = 16; // 不常用字段可以使用更大编号\r\n \r\n // 保留字段编号(避免将来冲突)\r\n reserved 3, 5, 9 to 11; // 保留特定编号\r\n reserved \"old_field_name\"; // 保留字段名\r\n}\r\n\r\n```"},{"id":"rpc","title":"Rpc","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"rpc","description":"","relativePath":"Tech/Code/rpc/rpc.md","rawContent":""},{"id":"4+1视图","title":"4+1视图","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":16,"slug":"41视图","description":"| 视图类型 | 描述 | | :-------------- | :------------------------------------------------------------------------------------------ | | 逻辑视图 | 逻辑视图面向系统逻辑分析和...","relativePath":"Tech/design/4+1视图.md","rawContent":"\r\n| 视图类型 | 描述 |\r\n| :-------------- | :------------------------------------------------------------------------------------------ |\r\n| 逻辑视图 | 逻辑视图面向系统逻辑分析和设计,是描述系统逻辑结构的视图,主要解决系统分析和设计的问题,它描述系统的**业务上下文**、系统的**逻辑分解**,以及分解出的**逻辑元素间的关系**。 |\r\n| 开发视图(实现视图) | 开发视图面向系统开发及软件管理,是描述**系统代码结构**,**构建结构**的视图,主要解决系统技术实现和开发的问题,它依托逻辑视图,描述代码、构建结构。 |\r\n| 运行视图(处理视图、行为视图) | 运行视图面向系统运行,是描述系统**启动过程**、**运行期交互**的视图,主要解决系统运行期交互,描述各**可执行交付件在运行期的交互关系**。 |\r\n| 部署视图(物理视图) | 部署视图面向系统部署,是描述系统的**交付**、**安装**、**部署**的视图,主要解决系统安装部署的问题,描述系统的交付、安装、部署关系。 |\r\n| 用例视图(场景视图) | 用例视图以用例作为驱动元素,驱动和验证其他四个视图的设计,**用例视图不增加设计元素,仅增加用例作为输入**,因此作为“+1”视图。 |\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250324213031.png)\r\n\r\n> [!note]\r\n> 白盒视图:逻辑视图、开发视图、运行视图都属于\r\n> 黑盒视图:部署视图、用例视图\r\n\r\n# 1. 用例视图\r\n用例视图是一种需求分析技术,通常采用UML的用例图进行设计。通过用例视图的设计过程,可以正确的识别系统的用户和其它系统(Actor)、系统边界(Boundary)和用例(Use Case),并对系统的功能场景进行充分的分析,以确定系统提供的功能可以满足用户需求。\r\n\r\n从文章开头的图中我们也能看到其他视图都是围绕用例视图展开的。用例视图之所以是4+1视图的**核心**,是因为它确定了以下信息,而其它4个视图都是需要围绕着这些信息进行设计:\r\n- **系统边界**:有了边界,才能够确定系统的设计范围;同时,通过边界能够识别出系统需要与用户或其它系统进行交互;\r\n- **系统用户**:明确的用户定义是系统需求分析的先决条件;\r\n- **功能和场景**:通过识别出系统与用户或其它系统的交互,可以分析出系统需要提供哪些功能,以及这些功能存在哪些应用场景;\r\n> [!note]\r\n> 结合DDD思想,我们可以通过事件风暴输出我们的领域事件(功能和场景),从而形成用例视图\r\n## 1.1. 用例模型画法\r\n| **组成元素** | **说明** | **符号表示** | **备注** |\r\n| ------------------ | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\r\n| 参与者(actor) | 参与者(actor)在建模过程中处于核心地位,actor是在系统之外与系统交互的人或事物。用一个小人表示。 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/68d3f09840b94ab3beb4f8d01cb472c7~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp#?w=166&h=188&e=png&b=fefefe) | 1. 找出参与者:参与者一定是**直接并主动地向系统发出动作并反馈**,否则就不是参与者。
2. 谁对系统有着明确的目标和要求并主动发出动作?
3. 系统为谁服务?业务工人(business worker):被动参与业务处于系统边界内部。 |\r\n| 用例 (User Case) | 用例定义了一组用例实例,每个实例都是系统所执行的一系列动作。用一个椭圆代表。 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8e6edeeadc4d4c108ff3a532c546f42d~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp#?w=302&h=128&e=png&b=fdfdfd) | 用例特征:1. 用例是相对独立的 2. 用例的执行结果对参与者来说是可观测和有意义的 3. 这件事必须由一个参与者发起 4.用例必然由动宾短语形式出现的 5.一个用例就是一个需求单元、设计单元、开发单元、测试单元,甚至部署单元
用例的粒度:1. 概念建模阶段:每个用例描述一个完整的事件流。2. 系统建模阶段:每个用例能够描述操作者与计算机的一次完整交互用例粒度的划分依据最标准的方法以该用例是否完成了参与者的某个完整目的为依据。
用例的来源:1. 一个明确有效的目标 2. 一个真实的目标应当完备的表达主角的期望 3. 一个有效的目标应当在系统边界内,由主角发动并具有明确的后果 |\r\n| 关联关系 (Association) | 表示参与者与用例之间的关系。用一个直线箭头表示 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c1e0891a2da349f2bd88bea8dabc64b6~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp#?w=568&h=242&e=png&b=fdfdfd) | |\r\n| 包含关系 (Include) | 表示一个**大的功能分解成多个小模块的动作**。用一个带包含文字的虚线箭头表示 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f149e3256a63488198cb89000861ca01~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp#?w=682&h=220&e=png&b=fdfdfd) | |\r\n| 扩展关系 (Extend) | 表示用例功能的延伸,相当于是**为用例提供附加功能**。用一个带扩展文字的虚线箭头表示 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0fc05e7a1b4f4bfcbad11e99a48c494e~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp#?w=664&h=220&e=png&b=fdfdfd) | |\r\n| 边界 | 用来表示正在建模系统的边界。边界内表示系统的组成部分,边界外表示系统外部。系统边界在画图中用方框来表示,同时附上系统的名称,参与者画在边界的外面,用例画在边界里面。 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a5d621a2ed7a4d70bae9418be5cf6bbd~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp#?w=1096&h=660&e=png&b=fefefe) | 边界是可大可小的,由建模者主观确定。边界的确定是一个动态的过程,没有明确的方法。需求过程是一个动态的过程,不可能一蹴而就,我们只能把这些不同的结果进行对比、思考、讨论,最终希望得到一个更恰当的结果,就像盲人摸象—样,多方结果的相互印证得出的结论总是会更接近真相。所以在建模过程中,如果对建模结果感到疑惑,就可以试着改变边界设定,得到不同的参与者和用例,再通过相互印证的方式得到更好的结果。系统边界的作用有时候不是很明显,在画图时可省略。 |\r\n## 1.2. 示例\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328215105.png)\r\n\r\n# 2. 逻辑视图\r\n用于描述系统的功能需求,即系统给用户**提供哪些服务**;以及描述系统软件功能拆解后的**组件关系**、**组件约束**和**边界**,反映系统整体组成与系统如何构建的过程。\r\n\r\n软件设计最重要的原则就是高内聚、低耦合,一个满足此原则的系统不应该存在不合理的依赖关系,比如下层与上层间的反向依赖,或是循环依赖等。\r\n\r\n一般,逻辑架构元素决定了开发组织(根据康威定律,反之亦然)。因此,逻辑元素的边界和接口也是后续多个开发组织之间进行接口控制的关系依据。设计合理的逻辑架构,可以提升团队的沟通效率,进而提升整个系统的交付效率和质量。\r\n## 2.1. 逻辑视图画法\r\n步骤分解顺序为:System > Susbsystem > Component > Module,对应着0层逻辑模型 -> 1层逻辑模型 -> 2层逻辑模型。\r\n### 2.1.1. 0层模型\r\n0层模型描述了System之间的关系,System与Subsystem包含关系。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328221622.png)\r\n### 2.1.2. 1层模型\r\n1层模型描述Subsystem之间的关系,Subsystem和Component包含关系。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328222226.png)\r\n### 2.1.3. 2层模型\r\n2层模型描述Component之间的关系,Component和module包含关系。\r\n## 2.2. 示例\r\njavaj体系中各个功能组件,以及他们的**层级关系**,**作用**和**依赖范围**。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328214901.png)\r\nspringcloud微服务的逻辑视图示例,描述了springcloud中各个功能组件。该逻辑视图描述了springcloud的关键组件、作用及相互依赖关系\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328214924.png)\r\n\r\n# 3. 开发视图\r\n主要包括两部分信息:\r\n- 对逻辑视图中功能模块,描述其代码位置,可以是代码仓位置,或代码目录,或是开源软件的版本信息等\r\n- 系统的构建,即如何将代码编译成二进制交付件(比如.so/.bin)。这个构建信息需要包括构建依赖、构建工具链、构建环境信息\r\n\r\n一个设计良好的开发视图,应该能够满足以下要求:\r\n- 通过逻辑视图的功能模块,能够找到它所有代码和所有的二进制交付件\r\n- 每一个代码源文件,都能够找到它所属的逻辑功能模块\r\n- 每一个二进制交付件,都能够找到它集成了哪些逻辑视图中的功能模块\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328222601.png)\r\n# 4. 运行视图\r\n逻辑视图、开发视图和部署视图,描述的都是系统的静态信息,到现在为止我们还缺少对系统动态行为的描述,而运行视图就是用来描述**系统中的动态信息的**。运行视图最常见的设计工具就是UML的时序图。\r\n\r\n运行视图的设计,最常见的是逻辑架构元素之间的交互关系,比如消息交互、服务调用或API调用。如下图所示。\r\n\r\n在运行视图中,除了要关注组件间的交互关系,通常还需要考虑并发、抢占、关键资源(比如锁)访问等。\r\n## 4.1. 组成元素\r\n\r\n| 元素 | 描述 | |\r\n| ------------------ | ------------------------------------------- | --- |\r\n| 进行角色(`Actor`) | 在交互中扮演特定角色的实体,可以是人、系统、外部组件等。 | |\r\n| 对象(`Object`) | 在交互中参与通信的实体,表示在系统中存在的具体对象,可以是类的实例、组件等。 | |\r\n| 生命线(`LifeLine`) | 表示对象在整个交互中的存在时间,是对象的生命周期,通常与对象的生存期一致。 | |\r\n| 控制焦点(`Activation`) | 表示对象活跃时的时间段,用于标识对象的操作或消息发送的时间段,用虚线框表示。 | |\r\n| 消息(`Message`) | 表示对象之间的通信或交互,可以是同步消息、异步消息、返回消息等。 | |\r\n| 自关联消息 | 表示对象发送消息给自身的情况,用于描述对象的自我调用或自身处理。 | |\r\n| 组合片段 | 用于表示在一段时间内特定的控制流程或行为,可以嵌套在时序图中,用来划分不同的交互片段。 | |\r\n\r\n时序图元素通过各种连接和注释,共同描述了对象之间的交互过程,以及它们的执行顺序和时序关系。这有助于开发人员更好地理解系统中的消息传递和协作情况。\r\n## 4.2. 示例\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328222725.png)\r\n\r\n# 5. 物理视图\r\n物理视图是软件架构中的一部分,用于描述系统的物理部署和运行环境。它关注于如何将软件系统部署在硬件设备上,并展示了不同组件、模块、服务在物理设备上的布局和交互关系。物理视图帮助开发人员和架构师理解系统的部署情况,以及硬件资源的利用情况。在UML中通常由部署图表示。\r\n## 5.1. 元素组成\r\n\r\n|元素类型|描述|示例|\r\n|---|---|---|\r\n|节点(Node)|物理或虚拟的计算资源,代表部署目标的硬件或软件环境|Web 服务器、数据库服务器,既可以是物理机器也可以是容器|\r\n|构件(Component)|系统中可部署和替换的模块单元|数据库组件、应用服务器组件|\r\n|节点关系(Node Relationship)|表示节点之间的连接或依赖关系|连接、依赖|\r\n|部署关系(Deployment Relationship)|节点与构件之间的部署关系|部署、关联|\r\n|网络(Network)|物理或逻辑的网络基础设施|局域网、互联网|\r\n|存储(Artifact)|被部署的物件(如文件、数据库表)|配置文件、数据文件|\r\n|线条(Connector)|构件之间的通信路径|连接器、通信路径|\r\n## 5.2. 示例\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328222938.png)\r\n"},{"id":"DDD","title":"DDD","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"ddd","description":"","relativePath":"Tech/design/DDD/DDD.md","rawContent":""},{"id":"DDD理论与实践","title":"DDD理论与实践","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"ddd理论与实践","description":"1. 什么是DDD DDD是一种处理高度复杂领域的ff0000\">设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD不是架构,而是一种架构ff0000\">设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域...","relativePath":"Tech/design/DDD/DDD理论与实践.md","rawContent":"# 1. 什么是DDD\r\nDDD是一种处理高度复杂领域的设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD不是架构,而是一种架构设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现架构演进。懂DDD模型设计的并不需要是一个专业的开发人员,而是需要一个领域专家,这个领域专家可以是产品、可以是资深的销售等等。\r\n# 2. 为什么要DDD\r\n可以利用DDD设计方法来建立领域模型,划分领域边界,再根据这些领域边界从业务视角来划分微服务边界。而按照DDD方法设计出的微服务的业务和应用边界都非常合理,可以很好地实现微服务内部和外部的「高内聚、低耦合」\r\n1. 统一团队成员术语,无论你是开发、测试、产品、解决方案等,避免沟通障碍。让文档更容易被理解,让代码的可读性更强。 => 输出件:名词解释表\r\n2. 抽象领域模型,聚焦核心业务,保护好业务的核心资产。让后期的代码改动尽可能远离核心领域。\r\n3. 指导我们更加合理地拆分微服务,划分软件实现边界——限界上下文\r\n# 3. 战略设计和战术设计\r\n- **战略设计主要从业务视角出发** ,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。\r\n- **战术设计则从技术视角出发** ,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根实体值对象领域服务应用服务资源库等代码逻辑的设计和实现。\r\n## 3.1. 战略设计\r\n### 3.1.1. 主要方法——事件风暴\r\n它是一个从发散到收敛的过程。\r\n**发散过程**: 它通常采用用例分析场景分析用户旅程分析,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系。\r\n**收敛的过程**:事件风暴过程会产生很多的**实体**、**命令**、**事件**等领域对象,我们将这些领域对象从不同的维度进行聚类,形成如**聚合**、**限界上下文等边界**,建立领域模型。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250309184449304.png)\r\n### 3.1.2. 达成效果——微服务的边界\r\n1. 在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。\r\n2. 根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。在这个图里,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界,所以用虚线表示。\r\n3. 根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。在这个图里,限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。\r\n# 4. DDD与微服务\r\nDDD是一种架构设计方法,微服务是一种架构风格。DDD通过事件风暴梳理出来限界上下文,会指导形成微服务的边界。"},{"id":"核心概念","title":"核心概念","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":21,"slug":"核心概念","description":"通用模型 NAT网关模型 1. 领域 汉语词典的解释:领域是从事一种专门活动或事业的范围 、部类或部门。 百度百科的解释:领域具体指一种特定的范围或区域。 两个解释有一个共同点——范围,领域的核心重点落在ff0000\">”域”字上,用来确定范围的,fff88f\">范围即边界 ,这也是DDD在设计中不...","relativePath":"Tech/design/DDD/核心概念.md","rawContent":"通用模型\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250306230257902.png)\r\nNAT网关模型\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250308225342750.png)\r\n\r\n# 1. 领域\r\n汉语词典的解释:领域是从事一种专门活动或事业的**范围** 、部类或部门。\r\n百度百科的解释:领域具体指一种特定的**范围**或区域。\r\n**两个解释有一个共同点——范围**,领域的核心重点落在”域”字上,用来确定范围的,范围即边界 ,这也是DDD在设计中不断强调边界的原因。\r\n1. DDD按照一定的规则**将业务领域进行细分**,分而治之。\r\n2. 当领域细分到一定的程度后,DDD会将问题范围**限定在特定的边界内**。\r\n3. 在这个**边界内**建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。\r\n> [!tip]\r\n> DDD的领域就是这个边界内要解决的业务问题域。举个例子,网络服务域解决的就是各类网络可达性和不可达性问题,计算服务域解决的是计算资源的分配与调度问题;存储服务域解决的就是数据存储和读取的问题。\r\n# 2. 子域\r\n既然领域是用来限定业务边界和范围的,那么就会有大小之分,领域越大,业务范围就越大,反之则相反。\r\n\r\nDDD的研究方法与自然科学的研究方法类似。当人们在自然科学研究中遇到复杂问题时,通常的做法就是将问题一步一步地细分,再针对细分出来的问题域,逐个深入研究 ,探索和建立所有子域的知识体系 。当所有问题子域完成研究时,我们就建立了全部领域的完整知识体系了。\r\n\r\n领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。对于子域来说还可以继续划分为”子子域“,直到划分为,我们熟悉的、能够快速处理的最小问题集合。这个划分到最小问题集合其实就是聚合,聚合之上就是限界上下文,根据限界上下文建立领域模型,然后设计微服务。\r\n> [!note]\r\n> 贯穿软件设计始终的思想:分而治之\r\n\r\n在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性功能属性划分为三类子域\r\n- 核心域\r\n- 通用域\r\n- 支撑域\r\n### 2.1.1. 核心域\r\n决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。\r\n> [!note]\r\n> 网络域的核心域——VPC、EIP、ELB、VPCEP、NAT、DC、CC\r\n### 2.1.2. 通用域\r\n没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。\r\n> [!note]\r\n> 十统一:用户认证、权限管理(IAM);标签管理\r\n### 2.1.3. 支撑域\r\n还有一种功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。简单来说就是从用户功能上不会直观体现,但是却必不可少。\r\n> [!note]\r\n> 网络域的通用域——CETUS、 NGP\r\n# 3. 限界上下文\r\n简单理解就是领域所处的环境以及邻域处理问题的边界。理论上,限界上下文的边界就是微服务的边界,因此,限界上下文在DDD设计中一个非常重要的概念。\r\n> [!note]\r\n> 网络域关于微服务拆分的优秀实践,包括CNOS和CETUS两个服务\r\n\r\n我们讨论一个问题,通常是在一个限界上下文内去讨论的。对于同一个概念,不同上下文会有不同的理解。\r\n> [!note]\r\n> 如在SNAT规则中,我们通常所说的”后端“是指NAT前的源地址(源主机);而在DNAT规则中,我们通常所说的”后端“是指NAT后的目的地址(目的real server)\r\n# 4. 实体\r\n- **特征**:拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。\r\n- **对象识别方法**:通过唯一标识符识别。\r\n- **生命周期**:对象的延续性和标识会跨越甚至超出软件的生命周期。\r\n- **可变性**:通常是可变的,除了唯一标识符外,其它属性都可以随时间变化。\r\n> [!note]\r\n> 在NAT网关这个领域下面,网关实例就是一种实体,其本身具有唯一ID,标识了一个NAT实例,这个NAT实例可以有port(网关口)、session_configuration(自定义会话属性)、TAG、名称、描述等等属性。\r\n\r\n与传统数据模型设计优先不同,DDD是针对实际业务场景构建实体对象和行为,先构建领域模型,再将实体对象映射到数据持久化对象。一个实体可以对应0个、1个或者多个PO对象模型。代码的具体实践中,实体又可以具体分为充血模型和贫血模型两种。\r\n## 4.1. 贫血模型\r\n贫血模型是指数据和业务逻辑分离。领域对象除了对象属性外,只有get和set方法(POJO)。所有的业务逻辑都不包含在内而是放在Service层。\r\n缺点:\r\n1. 所有逻辑都被抽象到了service层,包括对象最简单的CURD操作,不够OO(面向对象)。\r\n优点:\r\n1. 分层清晰,结构清楚\r\n2. 设计简单,领域模型对象非常稳定\r\n典型三层设计方法\r\n- Controller + VO\r\n- Service + BO\r\n- Repository + PO\r\n## 4.2. 充血模型\r\n充血模型是指数据和对应的业务逻辑被封装到同一个类中。因此,这种充血模型满足面向对象的封装特性,是典型的OO(面向对象)编程风格。\r\n优点:\r\n1. 符合面向对象设计,对象属性和对象行为绑定\r\n缺点\r\n1. 如何划分业务逻辑不够清楚,什么样的逻辑应该放在**Domain Object**中,什么样的业务逻辑应该放在**Service**中,这是很含糊的。\r\n> [!note]\r\n> 在实践中,我们常用贫血模型,并把对象原生具有的行为、且会被多次重用的行为方法抽象成domain service,以此来和一般的service(usecase)做区分。当然,这个domain service要能够做到不依赖具体基础设施层的实现,不依赖于领域对象外的其他对象模型。在Spring架构中,我们通常可以通过依赖倒置,让domainService依赖repository的接口来实现解耦。\r\n# 5. 值对象\r\n1. **特征**:无唯一标识,完全由其属性值定义\r\n2. **对象识别方法**:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体\r\n3. **生命周期**:没有独立的生命周期,通常作为实体的一部分\r\n> [!note]\r\n> 如NAT网关示例中TCP超时老化时间、UDP超时老化时间、ICMP超时老化时间、非SYN包是否RST、非SYN包是否建链等等一系列相关属性。如果这些属性单独来看,都会显得很碎,如果我们把这一系列属性组合成一个session_configuration集合来看,那这个集合就是值对象。\r\n## 5.1. 值对象的存储方式\r\n- 如果值对象**属性有被查询过滤**的诉求,那么,值对象的每个属性都需要在数据库里单独成为一列;\r\n- 相反,如果值对象作为**一个整体**,不会存在单独过滤值对象某个属性的场景,则可以将值对象作为一个大的json直接序列化到数据库中。\r\n# 6. 聚合\r\n在事件风暴中,我们抽象出我们领域需要的**实体**和**值对象** ==> 进而将业务关联紧密的实体和值对象**进行组合**,形成聚合 ==> 再根据业务语义将多个聚合划定到同一个限定上下文。聚合其实就位于限定上下文和实体之间的一个概念。\r\n\r\n聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的。\r\n- 一个聚合只有一个聚合根,聚合根是可以独立存在的\r\n- 聚合中其他实体或值对象依赖于聚合根\r\n- 只有聚合根才能被外部访问到,聚合根维护聚合的内部一致性\r\n\r\n聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的微服务很自然就是“高内聚、低耦合”的。\r\n\r\n聚合是位于限界上下文和实体中间的一个概念,一个限界上下文中可能会有多个聚合。一个限界上下文一般对应着一个微服务,随着微服务规模的不断扩大,可能面临进一步拆分的问题,这时候就可以以聚合为单位进行。\r\n> [!note]\r\n> NAT业务有NAT网关(公网和私网)、NETWORKEP两个聚合,未来可以根据聚合规模的发展情况,将聚合升级为限界上下文,从而完成微服务的进一步拆分。ELB服务有L4和L7两个聚合。\r\n\r\n## 6.1. 聚合原则\r\n1. 聚合用来封装**真正的不变性**,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。\r\n2. **设计小聚合**。如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。\r\n3. 通过**唯一标识引用其他聚合**。聚合之间是通过关联外部聚合根ID的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。\r\n4. 在**边界之外使用最终一致性**。聚合内数据强一致性,而聚合之间数据最终一致性。在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用**领域事件**的方式异步修改相关的聚合,实现聚合之间的解耦。\r\n5. 通过**应用层实现跨聚合的服务调用**。为实现微服务内聚合之间的解耦,以及未来以聚合为单位的微服务组合和拆分,应避免跨聚合的领域服务调用和跨聚合的数据库表关联。\r\n# 7. 聚合根\r\n如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。\r\n1. 首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。\r\n2. 其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。\r\n3. 最后在聚合之间,它还是聚合对外的接口人,以聚合根ID关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。\r\n也就是说,聚合之间通过聚合根ID关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。\r\n> [!note]\r\n> 如果简单直白来说,聚合根一般会以一级模型的形式出现。在NAT模型领域,NAT网关实例就是NAT业务的聚合根,SNAT和DNAT是聚合内的其他实体,要访问SNAT或DNAT,需要先找到网关实例,再找到SNAT和DNAT\r\n\r\n# 8. 领域事件\r\n领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。\r\n## 8.1. 识别方法\r\n在做用户旅程或者场景分析时,我们要捕捉业务、需求人员或领域专家口中的关键词:\r\n- 如果发生……,则……\r\n- 当做完……的时候,请通知……\r\n- 发生……时,则……\r\n在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件\r\n> [!note]\r\nPORT已删除 -> DNAT规则失效\r\nEIP已冻结 -> SNAT EIP池刷新\r\n用户已欠费 -> 网关冻结\r\n\r\n领域事件驱动设计可以切断领域模型之间的强依赖关系。事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。\r\n\r\n## 8.2. 微服务内\r\n- 过程调用 -> sk,任务流\r\n- 事件总线 -> cetus,kafka自发自收\r\n## 8.3. 微服务间\r\n- API调用(TCC,try-confirm-concel)\r\n- 消息中间件\r\n- 事件总线(Event-Bus)\r\n# 9. 领域服务\r\n领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。当某个操作不适合放在聚合和值对象上时,最好的方式便是使用领域服务了。可以使用领域服务的情况:\r\n- 执行一个显著的业务操作\r\n- 对领域对象进行转换\r\n- 以多个领域对象作为输入参数进行计算,结果产生一个值对象\r\n# 10. 应用服务\r\n应用层作为视图层领域层的桥梁,是用来表达用例(usecase)和用户故事的主要手段。\r\n\r\n应用层通过应用服务接口来暴露系统的全部功能。在应用服务的实现中,它负责**编排和转发**,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。\r\n\r\n应用层相对来说是较“薄”的一层,除了定义应用服务之外,在该层我们可以进行安全认证,权限校验,持久化事务控制,或者向其他系统发生基于事件的消息通知,另外还可以用于创建邮件以发送给客户等。\r\n\r\n> [!note]\r\n> 领域服务和应用服务的区别在于:\r\n> 1. 领域服务侧重于实现对一个或多个领域对象进行操作、计算的执行动作\r\n> 2. 应用服务侧重于编排,本身不应该包含复杂的执行动作\r\n\r\n"},{"id":"design","title":"Design","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"design","description":"","relativePath":"Tech/design/design.md","rawContent":""},{"id":"代码坏味道","title":"代码坏味道","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":5,"slug":"代码坏味道","description":"1. 重复代码 2. 长函数 3. large class 4. Divergent Change(发散式变化) 对程序进行维护时, 如果添加修改组件, 要同时修改一个类中的多个方法,那么这就是 Divergent Change。举个汽车的例子,某个汽车厂商生产三种品牌的汽车:BMW、Benz和La...","relativePath":"Tech/design/代码坏味道.md","rawContent":"# 1. 重复代码\r\n# 2. 长函数\r\n# 3. large class\r\n# 4. Divergent Change(发散式变化)\r\n对程序进行维护时, **如果添加修改组件, 要同时修改一个类中的多个方法**,那么这就是 Divergent Change。举个汽车的例子,某个汽车厂商生产三种品牌的汽车:BMW、Benz和LaoSiLaiSi,每种品牌又可以选择燃油、纯电和混合动力。\r\n\r\n如果某个模块经常因为不同的原因在**不同的方向上发生变化**,发散式变化就出现了。当你看着一个类说:“呃,如果新加入一个数据库,我必须修改这3个函数;如果新出现一种金融工具,我必须修改这 4 个函数。“这就是发散式变化的征兆。数据库交互和金融逻辑处理是两个不同的上下文,将它们分别搬移到各自独立的模块中,能让程序变得更好:每当要对某个上下文做修改时,我们只需要理解这个上下文,而不必操心另一个。“每次只关心一个上下文”这一点一直很重要,在如今这个信息爆炸、脑容量不够用的年代就愈发紧要。当然,往往只有在加入新数据库或新金融工具后,你才能发现这个坏味道。在程序刚开发出来还在随着软件系统的能力不断演进时,上下文边界通常不是那么清晰。\r\n****\r\n优化方案:\r\n- 遵循单一原则,拆分不同模块\r\n# 5. Shotgun Surgery(散弹式修改)\r\n当你实现某个小功能时,你需要在很多不同的类做出小修改。这就是**Shotgun Surgery(散弹式修改)**。这意味着需要修改的代码散布四处,你不但很难找到它们,也很容易错过某个重要的修改。\r\n> [!tip]\r\n> 跟**发散式变化(Divergent Change)** 的区别就是,它指的是同时对**多个类进行单一**的修改,发散式变化指在**一个类中因为不同功能模块变化需要修改多处**。\r\n\r\n优化方案:\r\n1. 所有需要修改的代码放进同一个模块中\r\n\r\n# 6. 基本类型偏执\r\n大多数编程环境都大量使用基本类型,即整数、浮点数和字符串等。一些库会引入一些小对象,如日期。但我们发现一个很有趣的现象:很多程序员不愿意创建对自己的问题域有用的基本类型,如钱、坐标、范围等。于是,我们看到了把钱当作普通数字来计算的情况、计算物理量时无视单位(如把英寸与毫米相加)的情况以及大量类似 if (a < upper && a > lower)这样的代码。\r\n\r\n字符串是这种坏味道的最佳培养皿,比如,电话号码不只是一串字符。一个体面的类型,至少能包含一致的显示逻辑,在用户界面上需要显示时可以使用。“用字符串来代表类似这样的数据”是如此常见的臭味,以至于人们给这类变量专门起了一个名字,叫它们“类字符串类型”(stringly typed)变量。\r\n> [!note]\r\n> 典型的坏味道是喜欢用map作为接口传参"},{"id":"设计原则与典型架构设计模型","title":"设计原则与典型架构设计模型","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":15,"slug":"设计原则与典型架构设计模型","description":"架构的设计本质都是为了高内聚、低耦合 1. SOLID原则 1.1. S单一职责原则(single responsibility) 一个class应该只做一件事,一个class应该只有一个变化的原因,核心是ff0000\">功能特性解耦和ff0000\">高内聚性。避免一个类承担两个特性,修改A特性的时...","relativePath":"Tech/design/设计原则与典型架构设计模型.md","rawContent":"架构的设计本质都是为了高内聚、低耦合\r\n# 1. SOLID原则\r\n## 1.1. S单一职责原则(single responsibility)\r\n一个class应该只做一件事,一个class应该只有一个变化的原因,核心是功能特性解耦高内聚性。避免一个类承担两个特性,修改A特性的时候导致B特性引入BUG。避免修复A问题,导致B问题修改的引入。\r\n## 1.2. O开闭原则(open-close)\r\nclass 应该对扩展开放对修改关闭。避免增加新功能导致修改引入。\r\n## 1.3. L里氏替换原则(Liskov Substitution Principle,LSP)\r\nIf S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。 如果S是T的子类,则T的对象可以替换为S的对象,而不会破坏程序。\r\n\r\nFunctions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。 所有引用其父类对象方法的地方,都可以透明的替换为其子类对象。\r\n\r\n\r\n## 1.4. I接口隔离原则(interface Segregation)\r\n隔离意味着保持独立,接口隔离原则是关于接口的独立。将一个万能的接口拆分成多个独立的接口,让子类可以选择性实现其中的部分接口。它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。\r\n> [!note]\r\n 接口隔离原则(Interface Segregation Principle,ISP)的定义是客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。简单来说就是建立单一的接口,不要建立臃肿庞大的接口。也就是接口尽量细化,同时接口中的方法尽量少。使用接口隔离原则前首先需要满足单一职责原则。\r\n## 1.5. D依赖倒置原则(Dependence Inversion)\r\n我们的class应该依赖接口和抽象类,而不是具体的类和函数。实现可以变,接口不能变。\r\n# 2. 设计原则\r\n设计模式和SOLID原则大部分是\r\n## 2.1. 迪米特法则\r\n迪米特法则(Law of Demeter )又叫做**最少知识原则**,也就是说,一个对象应当对其他对象尽可能少的了解。\r\n\r\n迪米特法则的目的在于**降低类之间的耦合**。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。\r\n\r\n迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在**大量的中介类**,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。\r\n# 3. DDD分层架构实现\r\n分层依赖原则,**每层只能与位于其下方的层发生耦合**,上层可以依赖下层,下层不能依赖上层。 \r\n> [!note]\r\n> TCP/IP协议栈的设计也是基于这种分层设计的思想\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250309194919779.png)\r\n## 3.1. 用户接口层\r\n用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。\r\n## 3.2. 应用层\r\n应用层是很薄的一层,理论上**不应该有业务规则或逻辑**,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。此外,应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。\r\n- 负责服务的组合、编排和转发\r\n- 负责处理业务用例的执行顺序以及结果的拼装\r\n- 以粗粒度的服务通过API网关向前端发布\r\n- 进行安全认证、权限校验、事务控制、发送或订阅领域事件等\r\n \r\n在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。因为**庞大的应用层会使领域模型失焦**,时间一长你的微服务就会**演化为传统的三层架构**,业务逻辑会变得混乱。\r\n## 3.3. 领域层\r\n领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。\r\n \r\n领域层中的实体会采用充血模型来实现所有与之相关的业务功能。当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。\r\n## 3.4. 基础层\r\n基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。\r\n\r\n基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,**降低外部资源变化对应用的影响**。\r\n\r\n> [!note]\r\n> 在传统架构设计中,由于上层应用对数据库的强耦合,很多公司在架构演进中最担忧的可能就是换数据库了,因为一旦更换数据库,就可能需要重写大部分的代码,这对应用来说是致命的。那采用依赖倒置的设计以后,应用层就可以通过解耦来保持独立的核心业务逻辑。当数据库变更时,我们只需要更换数据库基础服务就可以了,这样就将资源变更对应用的影响降到了最低。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250309194808555.png)\r\n\r\n# 4. CQRS(Query Command Responsibility Segregation)\r\n对与业务查询场景比较多、比较复杂的情况下,采用Command和Query模型分离的设计,可以使业务上进行一些解耦。命令和查询模型某种程度上比较相似,CQRS的设计可能会导致系统过于复杂。\r\n- Query模型一般可以只包括两层,DB视图和用户视图,将数据查询结果对象映射为接口响应模型。专用于查询的模型可以与领域模型解耦与数据查询方式和业务展示结果挂钩\r\n- 业务的复杂性导致我们查看的数据结构和存储的数据结构并不一致,如将多条记录合并为一条或通过联表查询组合成新记录。\r\n- 可以通过从OLTP数据库同步数据到OLAP数据,以实现Command和Query的分离。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250309222314123.png)\r\n优势:\r\n1. 提供吞吐和时延 => 查询分离,OLAP数据库(HBASE大宽表)\r\n2. 提供多个查询视图,避免业务间互相影响\r\n缺点:\r\n1. 数据同步时延、一致性的复杂性\r\n2. 代码编写的额外成本和复杂性增加\r\n# 5. 整洁架构(洋葱架构)\r\n整洁架构的层就像洋葱片一样,它体现了分层的设计思想。在整洁架构里,同心圆代表应用软件的不同部分,从里到外依次是领域模型、领域服务、应用服务和最外围的容易变化的内容,比如用户界面和基础设施。\r\n\r\n1. 整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,**越往里依赖越低,代码级别越高,越是核心能力** 。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。\r\n2. 每一层对外暴露接口,隐藏内部使用细节在较深的层定义抽象接口,在最外层提供具体实现。可以保证我们专注于领域模型,而不必过多地担心实现细节。可以通过类似于Spring之类的依赖注入框架在运行时将接口和实现连接起来。\r\n3. 关注点分离。应用被分为若干层,每一层都有不同的职责和关注点。\r\n4. 代码的可测性提高,可以单独每每一层构建测试用例\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250309200146466.png)\r\n\r\n- **领域模型**:实现领域内核心业务逻辑,它封装了企业级的业务规则。领域模型的主体是实体,一个实体可以是一个带方法的对象,也可以是一个数据结构和方法集合。\r\n- **领域服务**:实现涉及多个实体的复杂业务逻辑。\r\n- **应用服务**:实现与用户操作相关的服务组合与编排,它包含了应用特有的业务流程规则,封装和实现了系统所有用例。\r\n- **基础层**:最外层主要提供适配的能力,适配能力分为主动适配和被动适配。\r\n\t1. 主动适配主要实现外部用户、网页、批处理和自动化测试等对内层业务逻辑访问适配。\r\n\t2. 被动适配主要是实现核心业务逻辑对基础资源访问的适配,比如数据库、缓存、文件系统和消息中间件等。\r\n# 6. 六边形架构(端口适配器架构)\r\n六边形架构的核心理念是:**应用是通过端口与外部进行交互的** 。我想这也是微服务架构下API网关盛行的主要原因吧。也就是说,在下图的六边形架构中,红圈内的核心业务逻辑(应用程序和领域模型)与外部资源(包括 APP、Web 应用以及数据库资源等)完全隔离,仅通过适配器进行交互。它解决了业务逻辑与用户界面的代码交错问题,很好地实现了前后端分离。六边形架构各层的依赖关系与整洁架构一样,都是由外向内依赖。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250309200511191.png)\r\n六边形架构将系统分为内六边形和外六边形两层,这两层的职能划分如下:\r\n- 红圈内的六边形实现应用的核心业务逻辑;\r\n- 外六边形完成外部应用、驱动和基础资源等的交互和访问,对前端应用以 API 主动适配的方式提供服务,对基础资源以依赖倒置被动适配的方式实现资源访问。\r\n 六边形架构的一个端口可能对应多个外部系统,不同的外部系统也可能会使用不同的适配器,由适配器负责协议转换。这就使得应用程序能够以一致的方式被用户、程序、自动化测试和批处理脚本使用"},{"id":"1. 为什么要dpdk(Data Plane Development Kit)","title":"1. 为什么要Dpdk(Data Plane Development Kit)","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"1-为什么要dpdkdata-plane-development-kit","description":"1. 基于OS内核转发的劣势 1.1. 数据路径长,协议栈处理开销大 - 内核路径: 数据包需经过内核协议栈(如TCP/IP栈)的多层处理(链路层→网络层→传输层→应用层),涉及大量内存拷贝、校验和计算、上下文切换等操作。 - DPDK优化: 直接绕过内核,在用户空间处理数据包,省去协议栈的逐层解析...","relativePath":"Tech/dpdk/1. 为什么要dpdk(Data Plane Development Kit).md","rawContent":"# 1. 基于OS内核转发的劣势\r\n## 1.1. 数据路径长,协议栈处理开销大\r\n- **内核路径**: \r\n 数据包需经过内核协议栈(如TCP/IP栈)的多层处理(链路层→网络层→传输层→应用层),涉及大量内存拷贝、校验和计算、上下文切换等操作。\r\n- **DPDK优化**: \r\n 直接绕过内核,在用户空间处理数据包,省去协议栈的逐层解析,实现零拷贝(Zero-Copy)和批量处理,显著降低延迟。\r\n## 1.2. 中断与上下文切换\r\n- **内核模式**: \r\n 每个数据包到达时触发硬件中断,导致CPU频繁切换上下文(用户态↔内核态)。高负载时,中断风暴可能占用大量CPU资源。另外,在基于多线程的服务器设计框架中,线程间的调度也会产生频繁的上下文切换开销,同样,锁竞争的耗能也是一个非常严重的问题。\r\n- **DPDK优化**: \r\n 使用轮询模式(Poll Mode)替代中断,主动从网卡队列拉取数据,避免中断开销。结合CPU亲和性(Affinity)绑定核心,减少上下文切换。\r\n## 1.3. 内存访问效率低\r\n- **内核瓶颈**: \r\n 内核通过`sk_buff`结构管理数据包,存在多次内存拷贝。正常情况下,一个网络数据包从网卡到应用程序需要经过如下的过程:数据从网卡通过DMA等方式传到内核开辟的缓冲区,然后从内核空间拷贝到用户态空间,在 Linux 内核协议栈中,这个耗时操作甚至占到了数据包整个处理流程的 57.1%。传统服务器内存页为4K,容易出现cache miss。\r\n- **DPDK优化**: \r\n 利用**大页(HugePages)** 和 **预分配内存池**,用户空间直接访问网卡DMA缓冲区,实现零拷贝,减少内存访问延迟。\r\n## 1.4. 局部性失效、核竞争\r\n- **内核锁竞争**: \r\n 内核协议栈的共享数据结构(如Socket缓冲区)需加锁保护,多核并发时锁竞争严重,扩展性差。一个数据包的处理可能跨多个CPU核心,比如一个数据包可能中断在cpu0,内核态处理在cpu1,用户态处理在cpu2,这样跨多个核心,容易造成 CPU缓存失效,造成局部性失效。如果是NUMA架构,更会造成跨NUMA访问内存,性能受到很大影响。\r\n- **DPDK优化**: \r\n 采用**无锁队列(Lock-free Ring)** 和 **每核独享资源**(如单独队列、内存池),支持线性扩展至数十个核心。\r\n## 1.5. 确定性延迟难以保证\r\n- **内核不确定性**: \r\n 内核调度、中断处理、任务抢占等机制引入随机延迟,难以满足超低延迟需求(如高频交易、5G控制面)。\r\n- **DPDK优化**: \r\n 通过独占CPU核心、禁用中断、实时线程优先级等手段,实现**微秒级确定性延迟**。\r\n## 1.6. 吞吐量瓶颈\r\n- **内核极限**: \r\n 传统内核协议栈的单核吞吐量通常在1-2Mpps(每秒百万包),难以应对高速网卡(如100Gbps、200Gbps)。\r\n- **DPDK优化**: \r\n 单核可处理10Mpps以上,结合多核负载均衡,充分发挥高速网卡性能。\r\n## 1.7. 硬件卸载支持有限\r\n- **内核支持滞后**: \r\n 对新硬件特性(如GPUDirect RDMA、智能网卡功能)的支持需等待内核更新。\r\n- **DPDK优势**: \r\n 通过PMD(Poll Mode Driver)直接管理网卡,快速集成硬件加速功能(如TSO、CRC校验卸载)\r\n# 2. DPDK局限性\r\n虽然DPDK性能优势显著,但也需付出代价:\r\n1. **独占CPU核心**:需预留专用核心运行轮询线程,可能浪费资源。\r\n2. **生态依赖**:需特定硬件(如支持SR-IOV的网卡)和驱动支持。\r\n3. **协议栈缺失**:需自行实现TCP等复杂协议\r\n# 3. 对比\r\n| **场景** | **内核传输** | **DPDK** |\r\n| ------- | ----------------- | -------------------- |\r\n| 高吞吐/低延迟 | ❌ 性能瓶颈 | ✅ 单核10Mpps+,微秒级延迟 |\r\n| 协议栈功能需求 | ✅ 完整TCP/IP支持 | ❌ 需自行实现部分协议(如TCP) |\r\n| 开发复杂度 | ✅ 标准Socket API,易用 | ❌ 需学习DPDK API,定制化成本高 |\r\n| 硬件兼容性 | ✅ 广泛支持 | ❌ 依赖特定网卡(如Intel NIC) |\r\n| 实时性要求 | ❌ 受调度影响 | ✅ 确定性延迟 |"},{"id":"ConfigMap","title":"ConfigMap","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"configmap","description":"1. 创建 1.1. 通过kubectl命令行创建 1.1.1. --from-file参数从文件中进行创建 其中key=是可选的,默认key就是文件名,通过key=可以指定key。 1.1.2. --from-file参数从目录中进行创建 目录下每个配置文件名都被设置为key,文件的内容设成为va...","relativePath":"Tech/Kubernetes/ConfigMap.md","rawContent":"# 1. 创建\r\n## 1.1. 通过kubectl命令行创建\r\n### 1.1.1. --from-file参数从文件中进行创建\r\n```shell\r\n# kubectl create configmap NAME --from-file=[key=]source --from-file=[key=]source\r\nkubectl create configmap application.yml --from-file=server.xml\r\n```\r\n其中key=是可选的,默认key就是文件名,通过key=可以指定key。\r\n### 1.1.2. --from-file参数从目录中进行创建\r\n目录下每个配置文件名都被设置为key,文件的内容设成为value,语法如下:\r\n```shell\r\n# kubectl create configmap NAME --from-file=config-files-dir\r\nkubectl create configmap spring-config --from-file=/etc/config\r\n```\r\n### 1.1.3. --from-literal直接指定key=value\r\n```shell\r\nkubectl create configmap NAME --from-literal=key1=value1 --from-literal=key2=value2\r\n```\r\n## 1.2. 通过yaml文件创建\r\n指定key,value形式\r\n```yaml\r\napiVersion: v1\r\nkind: ConfigMap\r\nmetadata:\r\n name: spring-appvars\r\ndata:\r\n apploglevel: info\r\n appdatadir: /var/data\r\n```\r\n指定配置文件\r\n```yaml\r\napiVersion: v1\r\nkind: ConfigMap\r\nmetadata:\r\n\t name: spring-configfiles\r\ndata:\r\n application.yml: |\r\n server:\r\n port: 8081\r\n spring:\r\n profiles:\r\n active: prod\r\n```\r\n# 2. 使用\r\n容器应用对configMap使用有以下两种方法\r\n- 通过环境变量获取configMap中的内容\r\n- 通过volume挂载的方式将configmap中的内容挂载为容器内部的文件或目录\r\n## 2.1. 通过环境变量方式使用\r\n创建configMap\r\n```shell\r\nkubectl create configmap active-profile --from-literal=active_profile=prod\r\n```\r\n定义Pod并引用configMap\r\n```yaml\r\napiVersion: v1\r\nkind: Pod\r\nmetadata:\r\n name: spring-k8s-test\r\nspec:\r\n containers:\r\n - name: spring-test\r\n image: kimb88/hello-world-spring-boot\r\n ports:\r\n - containerPort: 8080\r\n\t envFrom:\r\n\t - configMapRef:\r\n\t name: active-profile\r\n```\r\n查看容器环境变量\r\n```bash\r\nroot@spring-k8s-test:/usr/local/tomcat# echo $active_profile\r\nprod\r\n```\r\n### 2.1.1. spring配置文件使用环境变量\r\n在spring中可以通过`${key:defaultValue}`的方式直接引用系统的环境变量\r\n```yaml\r\nserver:\r\n port: 8080\r\nspring:\r\n profiles:\r\n active: ${active_profile}\r\n```\r\n## 2.2. 通过volumeMount方式使用\r\n```yaml\r\napiVersion: v1\r\nkind: Pod\r\nmetadata:\r\n name: spring-k8s-test\r\nspec:\r\n containers:\r\n - name: spring-test\r\n image: kimb88/hello-world-spring-boot\r\n ports:\r\n - containerPort: 8080\r\n volumeMounts:\r\n - name: application-prod # 引用volume名称\r\n mountPath: /etc/config # 挂载到容器内目录\r\n volumes:\r\n - name: application-prod\r\n configMap:\r\n name: spring-configfiles\r\n items:\r\n - key: application.yml # 配置项key\r\n path: application.yml # 配置项value的内容将生成到名称application.yml的文件中\r\n```\r\n> 如果不指定items,则使用volumeMount方式在容器内的目录为每个item都生成一个文件名为key的文件。\r\n\r\n```\r\nroot@spring-k8s-test:/usr/local/tomcat# ls /etc/config\r\napplication.yml\r\nroot@spring-k8s-test:/usr/local/tomcat# cat /etc/config/application.yml\r\nserver:\r\n port: 8081\r\nspring:\r\n profiles:\r\n active: prod\r\n```\r\n## 2.3. 限制\r\n- configMap必须在pod之前创建,Pod才能引用\r\n- 如果Pod使用envFrom基与configMap定义的环境变量,则无效的环境变量名称将被忽略,并在事件中被记录为InvalidVariableNames\r\n- configMap受命名空间限制,只有处于相同命名空间中的Pod才可以引用它\r\n- configMap无法用于静态Pod\r\n"},{"id":"ingress Gateway","title":"Ingress Gateway","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"ingress-gateway","description":"在Kubernetes的服务网格架构中,Ingress Gateway是通过Envoy实现的。在Istio中,Ingress Gateway 是用于管理进入服务网格的流量的组件,它基于 Envoy 代理构建。Envoy 是一个高性能的代理,专门设计用于处理服务到服务的通信,支持动态服务发现、负载均衡...","relativePath":"Tech/Kubernetes/ingress Gateway.md","rawContent":"在Kubernetes的服务网格架构中,Ingress Gateway是通过Envoy实现的。在Istio中,Ingress Gateway 是用于管理进入服务网格的流量的组件,它基于 Envoy 代理构建。Envoy 是一个高性能的代理,专门设计用于处理服务到服务的通信,支持动态服务发现、负载均衡、TLS 终止、HTTP/2 和 gRPC 代理、熔断器、健康检查、灰度发布、故障注入和丰富的指标收集等功能。\r\n\r\nk8s中istiod和envoy就是典型的控制平面和数据平面的关系。用户通过声明式的方法调用kubernetes api server,istiod将配置转化为数据面能理解的数据,envoy通过xds协议主动从istiod拉去数据。"},{"id":"kube-proxy和istio envoy","title":"Kube Proxy和Istio Envoy","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"kube-proxy和istio-envoy","description":"- 每个节点安装了一个kube-proxy - 每个pod以sidecar的形式部署一个envoy - kube-proxy拦截的是进出kubernetes节点的流量,envoy拦截的是进出pod的流量。 - istio作为服务网格的一种实现,本质上提供了应用间的流量、安全管理和可观察性。 1. k...","relativePath":"Tech/Kubernetes/kube-proxy和istio envoy.md","rawContent":"- 每个节点安装了一个kube-proxy\r\n- 每个pod以sidecar的形式部署一个envoy\r\n- kube-proxy拦截的是进出kubernetes节点的流量,envoy拦截的是进出pod的流量。\r\n- istio作为服务网格的一种实现,本质上提供了应用间的流量、安全管理和可观察性。 \r\n# 1. kube-proxy\r\nK8s集群中每个节点部署一个kube-proxy组件,与k8s api server进行通信,获取集群中的服务信息,然后设置iptables规则,将服务请求直接发送到对应的endpoint。\r\n# 2. Istio\r\n"},{"id":"Kubernetes 核心概念","title":"Kubernetes 核心概念","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":11,"slug":"kubernetes-核心概念","description":"kubernetes中的基本概念和术语大多是围绕资源对象来说的,而资源对象在总体上可以分为以下两类: (1)某种资源的对象,例如节点(Node),Pod,服务(Service),存储卷(Volume) (2)与资源对象相关的事物与动作,例如标签(Label)、注解(Annotation)、命名空间(...","relativePath":"Tech/Kubernetes/Kubernetes 核心概念.md","rawContent":"kubernetes中的基本概念和术语大多是围绕资源对象来说的,而资源对象在总体上可以分为以下两类:\r\n(1)某种资源的对象,例如节点(Node),Pod,服务(Service),存储卷(Volume)\r\n(2)与资源对象相关的事物与动作,例如标签(Label)、注解(Annotation)、命名空间(namespace)、部署(Deployment)、HPA(Horizontal Pod Autoscaling,Pod水平自动伸缩)、PVC(dynamically provisioned claim)\r\n# 1. 资源对象通用属性\r\n版本:版本信息里面包括了此对象所属的资源组\r\n类型(Kind):定义资源对象类型\r\n元数据:\r\n - 资源对象的名称要唯一\r\n - 资源对象的标签表明资源对象的特征、类别,以及通过标签筛选不同的资源对象并实现对象之间的关联、控制和写作功能。\r\n - 注解可悲理解为一种特殊的标签,不过更多地是与程序挂钩,通常用于实现资源对象属性的自定义扩展。\r\n## 1.1. 声明\r\n可以通过YAML或JSON格式生命一个k8s的资源对象,每一个资源对象都有自己的特定结构定义,并保存在etcd二中非关系型数据库中,以实现最快的读写速度。所有资源对象都可以通过k8s提供的kubectl工具执行增、删、改、查等操作。\r\n## 1.2. 状态\r\n比如POD,通过kubectl客户端工具创建一个Pod并将其提交到系统中后,它就处于等待调度的状态,调度成功后为pending状态,等待容器镜像下载和启动、启动成功后为Running状态,正常停止后为Succeeded状态,非正常停止后为Failed状态。\r\n# 2. 资源对象分类\r\n- 集群类\r\n- 应用类\r\n- 存储类\r\n- 安全类\r\n## 2.1. 集群类\r\n在集群管理方面,Kubernetes将集群中的机器分为一个Master和一些Node。\r\n### 2.1.1. Master\r\nMaster指的是集群的控制节点,在每个K8s集群中都需要有一个或一组被称为Master的节点,来负责整个集群的管理和控制。Master通常占据一个独立的服务器(在高可用部署中建议至少使用3台服务器),是整个集群的“大脑”。\r\n在Master上运行着集群管理相关的一些进程:kube-apiserver、kube-controller-manager和kube-scheduler。\r\n- kube-apiserver:提供HTTP RESTful API接口的主要服务,是K8s里对所有资源进行增、删、改、查等操作的唯一入口,也是集群控制的入口进程。\r\n- kube-controller-manager:k8s里所有资源对象的自动化控制中心,可以将其理解为资源对象的“大总管”。\r\n- kube-scheduler:负责资源调度(Pod调度)的进程,相当于公交公司的调度室。\r\n这些进程实现了整个集群的资源管理、Pod调度、弹性伸缩、安全控制、系统监控和纠错等管理功能,并且都是自动完成的。另外,在Master上通常还要部署etcd服务。\r\n### 2.1.2. Node\r\nk8s集群中除Master外的其他服务器被称为Node,Node作为集群中的工作节点,其上运行着真正的应用程序。与Master一样,Node可以是一台物理主机,也可以是一台虚拟机。Node是k8s集群中的工作负载节点,每个Node都会被Master分配一些工作负载(Docker容器)。当某个Node宕机时,其上的工作负载会被Master自动转移到其他Node上。在每个Node上都运行这以下关键进程,这些服务进程负责Pod的创建、启动、监控、重启、销毁,以及实现软件模式的负载均衡。k\r\n- kubelet: 负责Pod对应的容器的创建、启动等任务,同时与Master密切写作、实现集群管理的基本功能。\r\n- kube-proxy:实现k8s service的通信与负载均衡机制的服务。\r\n- 容器运行时(如Docker):负责本机的容器创建和管理\r\n### 2.1.3. 命名空间\r\n命名空间可以用于实现多租户的资源隔离,典型的一种思路就是给每个租户分配一个命名空间,命名空间属于k8s集群范畴的资源对象,在一个集群里可以创建多个命名空间,每个命名空间都是互相独立的存在,属于不同命名空间的资源对象从逻辑上相互隔离。在每个k8s集群安装完成且正常运行之后,Master会自动创建两个命名空间,一个是默认的(default)、一个是系统级的(kube-system)。用户创建的资源对象如果没有指定命名空间,则被默认存放在default命名空间中,而系统相关的资源对象如网络组件、DNS组件、监控类组件存放在kube-systemc命名空间中。我们可以通过命名将集群内部的资源对象分配到不同的命名空间中,形成逻辑上分组不同的项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时能够被分别管理,当给每个租户都创建一个命名空间来实现多租户的资源隔离时,还能结合k8s的资源配额管理,限定不同租户能占用的资源,如cpu使用量和内存使用量。\r\n## 2.2. 应用类\r\n### 2.2.1. Service\r\n一个service对象拥有以下关键属性\r\n- 一个全局唯一的服务名称\r\n- 一个vip(cluster ip)和端口\r\n- 提供某种远程服务能力\r\n- 能够将客户端对服务端的访问请求转发到一组容器应用上\r\n一般情况下service指定的是无状态服务,通常由多个程序副本提供服务,在特殊情况下也可以是有状态的单实例服务,比如Mysql这种数据存储类的服务。Service的每个服务进程有独立的ip和port,service具有一个全局唯一的clusterIP,service一旦被创建,k8s就会自动为他分配一个可用的clusterIP地址,而且在service的整个生命周期中,这个地址都不会变。借助k8s集群的DNS服务,就可以实现service name(域名)到clusterIP地址的DNS映射功能,kubernetes能够让我们通过service(cluster ip + port)连接指定服务,提供透明的负载均衡和故障恢复机制。\r\n### 2.2.2. pod对象\r\nPod运行在一个被称为节点(Node)的环境中,这个节点(Node)既可以是物理机,也可以是云上的某个虚拟机,一个节点上能够运行多个Pod。其次,一个Pod包含一组容器,在每个Pod中都会运行一个特殊的叫Pause的容器,其他容器则为业务容器,这些业务容器共享Pause容器的网络栈和Volume挂载卷,因此他们之间的通信和数据交换更为高效,在设计时我们可以充分利用这一特性将一组密切相关的服务进程放进同一个Pod中。最后,需要注意的是不是每个pod和它里面运行的容器都能够被映射到一个service上,只有需要提供服务的那组Pod才会被映射为一个service。\r\n### 2.2.3. PodIP,NodeIP,ClusterIP\r\nNodeIP是k8s集群中的每个节点的物理网卡的IP地址,是一个真实存在的物理网络,所有属于这个物理网络的主机(不管这个主机在不在k8s集群内)都可以直接通信。因此,k8s集群外的节点访问k8s集群内的某个节点的TCP/IP服务时,都必须通过NodeIP通信。\r\nPodIP是每个POD的IP地址,在使用Docker作为容器支持引擎的情况下,是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟二层网络。K8s集群里面的Pod容器是可以互相访问的,借助了NodeIP构建了虚拟二层网络。\r\n在k8s集群里,service的clusterIP地址属于集群内的地址,无法在集群外直接使用这个地址,k8s引入了NodePort的概念,在每个Node上为需要外部访问的service开启一个对应的TCP监听端口,外部系统使用任意一个Node的IP+NodePort即可访问此服务。如果集群中存在多个Node情况,则需要外挂一个负载均衡器,这个负载均衡器一般独立于k8s集群之外,通过是一个硬件的负载均衡器或者软件实现的haproxy或nginx。NodePort功能强大,但每个Service都需要在Node上独占一个端口,而端口是有限的物理资源,于是引入了ingress来让多个service复用端口。\r\n每个service都会分配一个clusterIp,clusterIp的负载均衡是源端负载均衡,在client所在Node上通过iptables或ipvs选择后端pod。\r\n### 2.2.4. service和pod关系"},{"id":"Pod调度","title":"Pod调度","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"pod调度","description":"k8s Master上的Scheduler服务负责实现Pod调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通过我们无法知道Pod最终会被调度到哪个节点上。 1. NodeSelector 可以实现将Pod调度到一些指定的Node上,可以...","relativePath":"Tech/Kubernetes/Pod调度.md","rawContent":"k8s Master上的Scheduler服务负责实现Pod调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通过我们无法知道Pod最终会被调度到哪个节点上。\r\n# 1. NodeSelector\r\n可以实现将Pod调度到一些指定的Node上,可以通过Node的标签和Pod的nodeSelector属性匹配实现。\r\n```shell\r\nkubectl lable nodes =\r\n```\r\n如果我们给多个Node都定义了相同的标签,则scheduler会根据调度算法从这组Noed中挑选一个可用的Node进行Pod调度。\r\n# 2. Affinity\r\n亲和型调度包括节点亲和性(NodeAffinity)和Pod亲和性(PodAffinity)两个维度。\r\n- 可以使用软限制、优先采用等限制方式,代替之前的硬限制。这样调度器在无法满足优先需求的情况下,会退而求其次,继续运行改Pod\r\n- 可以依据节点上正在运行的其他Pod的标签进行限制,而非节点本身的标签,这样就定义一种规则来描述Pod之间的亲和和互斥关系。\r\n## 2.1. NodeAffinity\r\n目前有两种节点亲和性表达\r\n- RequiredDuringSchedulingIgnoredDuringException: 必须满足指定的规则才可以调度Pod到Node上,属于硬限制\r\n- PreferredDuringSchedulingIgnoredDuringException: 强调优先满足指定规则,调度器会尝试调度Pod到Node上,但并不强求,相当于软限制。多个优先级规则还可以设置权重值,以定义执行的先后顺序。\r\n> IgnoredDuringException意思是一个Pod所在Node节点在Pod运行期间标签发生了变更,不再符合该Pod的节点亲和性需求,则系统将忽略Node上Label的变化,该Pod能继续在节点上运行。\r\n\r\n## 2.2. PodAffinity\r\n简单地说,就是相关联的两种或多种Pod是否可以在同一个拓扑域中共存或者互斥,前者被称为Pod Affinity,后者被称为Pod Anti Affinity。"},{"id":"Service","title":"Service","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"service","description":"一个service对外暴露一个,client可以访问这个来实现负载均衡访问后端Pod。一个service对应的“后端”由pod的ip和端口号组成,这在kubernetes中称为endpoint。一组endpoints形如,其中和都是podIp。","relativePath":"Tech/Kubernetes/Service.md","rawContent":"一个service对外暴露一个`clusterIp:port`,client可以访问这个`clusterIp:port`来实现负载均衡访问后端Pod。一个service对应的“后端”由pod的ip和端口号组成,这在kubernetes中称为endpoint。一组endpoints形如`10.0.95.22:8080,10.0.95.23:8080`,其中`10.0.95.22`和`10.0.95.23`都是podIp。"},{"id":"postgresql-DML","title":"Postgresql DML","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"postgresql-dml","description":"1. 常用运维sql","relativePath":"Tech/Middleware/Database/postgresql-DML.md","rawContent":"# 1. 常用运维sql\r\n```sql\r\n# 查询单表实际占用物理空间\r\nselect pg_size_pretty(pg_total_relation_size('table_name'));\r\n# 查询单表的最大表页\r\nselect max(ctid) from table_name;\r\n```\r\n"},{"id":"性能优化","title":"性能优化","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"性能优化","description":"1. 查看数据库CPU有没有冲高,一般是有慢SQL 2. 查看数据库I/O有没有冲高,如果有一般是用了外部排序 3. 优化外部排序有两个思路,一个是添加索引,避免临时排序;第二个是限制数据规模,尽量保证在内存中完成排序 4. 先联表后排序,索引会无法利用;要做利用索引做子查询排序,然后再联表 5. ...","relativePath":"Tech/Middleware/Database/性能优化.md","rawContent":"1. 查看数据库CPU有没有冲高,一般是有慢SQL\r\n2. 查看数据库I/O有没有冲高,如果有一般是用了外部排序\r\n3. 优化外部排序有两个思路,一个是添加索引,避免临时排序;第二个是限制数据规模,尽量保证在内存中完成排序\r\n4. 先联表后排序,索引会无法利用;要做利用索引做子查询排序,然后再联表\r\n5. 限制数据规模,可以先做子查询,后联表实现。排序在子查询中做完后,联表后排序乱了,还要再次排序。\r\n"},{"id":"数据库关键配置","title":"数据库关键配置","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"数据库关键配置","description":"1. socketTimeout 未配置socketTimeout时,应用不会超时,会永远等待。当出现网络隔离故障时,服务端访问数据库的连接会hang住,导致需要等到tcp重传失败或者keepalive失败才会断开连接,故障时间会非常长。 数据库连接池中的连接可能处于如下两种状态 - 连接处于繁忙状...","relativePath":"Tech/Middleware/Database/数据库关键配置.md","rawContent":"# 1. socketTimeout\r\n未配置socketTimeout时,应用不会超时,会永远等待。当出现网络隔离故障时,服务端访问数据库的连接会hang住,导致需要等到tcp重传失败或者keepalive失败才会断开连接,故障时间会非常长。\r\n\r\n数据库连接池中的连接可能处于如下两种状态\r\n- 连接处于繁忙状态(数据传输状态),会启动[[Tcp重传]],默认情况下需要约15min才可以断开连接。\r\n- 连接处于空闲状态(idle),启用了keepalive机制,默认配置下需要[[Tcp KeepAlive|7975]]秒,约2个多小时才会断开。\r\n\r\n应用对socket中数据的读写操作都是阻塞的,socketTimeout即是读写socket底层数据时的阻塞超时时间。服务端可以通过配置socketTimeout来控制读或写数据的超时时间,避免陷入tcp重传或tcp keepalive,从而导致长时间故障。\r\n> [!note]\r\nsocketTimeout的时间一般以应用最长sql等待时间为准,也就是**最慢的那条sql**的执行时间为参考。同时,考虑到API接口的超时时间,可以设置为30秒。如果是时延敏感型应用,这个时间还可以设置得更短一点。\r\n## 1.1. soketTimeout与tcp_retries2\r\n如果我们在应用层设置的socketTimeout很小的话(例如3s),tcp_retries这个内核参数调整是没有必要的。\r\n\r\n【木桶效应】但是,有些系统会因为一两个慢的接口或者SQL,从而将socketTimeout设的很大。\r\n\r\n物理机突然宕机时,由于soketTimeout设置的过大,导致所有落到这台宕机的机器都会在$min(soketTimeout,924.6s-1044.6s)$,Linux默认tcp_retries2是15后才能从read系统调用返回。假设soketTimeout设置了个5min,系统总线程数是200,那么只要5min内有200个请求落到宕机的server就会使系统失去响应!\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250323173815.png)\r\n这时候我们可以通过将tcp_retries2设置为5,那么超时返回时间即为$min(soketTimeout假设为 5min,25.6-51.2s)$,也就是30s左右。\r\n```bash\r\necho 5 > /proc/sys/net/ipv4/tcp_retries2\r\n```\r\n此情况下:\r\n- 在慢SQL场景下,客户端和数据库服务器之间的 TCP 连接实际上是正常的,数据包能够正常发送和接收,只是数据库处理请求的时间过长。因此,不会出现TCP重传的情况,`tcp_retries2`参数也就不会发挥作用,连接超时只会受`socketTimeout`控制。\r\n- 如果出现物理主机宕机或网络故障,tcp连接异常,会进入tcp重传,此时所有的的线程都可以在约30秒发现故障。\r\n## 1.2. socketTimeout底层时间\r\n底层调用`java.net.Socket.setSoTimeout(int timeout)`方法进行设置,官方解释如下\r\n> With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.\r\n\r\n在linux中,通常是通过调用[[Tcp重传#5. tcp user timeout|setsetsockopt]]实现的。\r\n# 2. connectTimeout\r\n控制**创建tcp连接**最大等待超时,默认永远等待。\r\n- 如果未配置connectTimeout,但jdbc url中配置了多个数据库ip的情况下,第一个数据库节点如果故障了,将会永远卡在连接第一个节点上,而无法逃生至后续节点。\r\n- 如果配置connectTimeout,且服务端jdbc url中配置了N个数据库节点ip,在前N-1个节点故障失联的情况下,最长可能会故障$(N-1)*connectTimeout$\r\n> [!note]\r\n应用一般会配置健康检查或看门狗,在应用无响应的情况下重新拉起进程。这种情况下,一定要保证$(N-1)*connectTimeout$大于健康检查或看房狗允许的最长启动等待时间,避免健康检查或看门狗失败反复拉起进程,导致应用没有恢复的机会。一般tcp建链需要三次握手,时间不可能很长,因此建议配置值为5秒。\r\n# 3. loginTimeout\r\n数据库用户成功登录到数据库服务器的超时时间,一定要保证$connectTimeout < loginTimeout < getConnectionTimeout$"},{"id":"数据库理论","title":"数据库理论","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"数据库理论","description":"1. 三大范式 - 第一范式(1NF) 保证列的原子性,一张表的某个列不能有多个值,不能出现重复列。实际操作中我们可能会违反这个约束,如将列值存成一个json字典,但是我们一定要保证这些值至少是一起读或写的,不存在经常部分更新的场景。 - 第二范式(2NF): 取消部分依赖,表中的每个字段都与主键相...","relativePath":"Tech/Middleware/Database/数据库理论.md","rawContent":"# 1. 三大范式\r\n\r\n- 第一范式(1NF)\r\n\t保证列的**原子性**,一张表的某个列不能有多个值,不能出现重复列。实际操作中我们可能会违反这个约束,如将列值存成一个json字典,但是我们一定要保证这些值至少是一起读或写的,不存在经常部分更新的场景。\r\n- 第二范式(2NF):\r\n\t**取消部分依赖**,表中的每个字段都与主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。比如订单表中可以用订单id编号和商品编号做联合主键,这时候表中的商品价格就是部分依赖,其只依赖于商品编号。\r\n- 第三范式(3NF):\r\n\t**取消传递依赖**,表中的每个字段都与主键直接相关。第三范式最严格,能够最大程度地减少数据冗余,但是也带来了不必要的查询开销。比如订单表中添加客户编号作为外键,但是不能把客户的手机号、地址等信息存入,因为这些额外的信息不依赖订单主键,只依赖于客户编号这个外键,属于传递依赖。所以第三范式使用的时候一定要综合考虑好空间和效率的平衡。\r\n\r\n# 2. DML,DDL,DCL\r\n\r\n- DML(data manipulation language):SELECT、UPDATE、INSERT、DELETE,就象它的名字一样,这4条命令是用来对数据进行操作的语言\r\n- DDL(data definition language):\r\n\tDDL比DML要多,主要的命令有CREATE、ALTER、DROP等。DDL主要是用在定义或改变表的结构,数据类型,表之间的链接和约束等初始化工作上,他们大多在建立表或软件版本更新时使用。\r\n- DCL(data control language):\r\n\tDCL是数据库控制功能,用来设置或更改数据库用户或角色权限的语句,包括grant,deny,revoke等语句。在默认状态下,只有sysadmin,dbcreator,db_owner或db_securityadmin等人员才有权力执行。\r\n"},{"id":"etcd","title":"Etcd","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"etcd","description":"","relativePath":"Tech/Middleware/etcd/etcd.md","rawContent":""},{"id":"etcd常见用法","title":"Etcd常见用法","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"etcd常见用法","description":"1. 数据put和get 1.1. 设置key和value 1.2. 获取指定key 1.3. 获取前缀下所有key 2. 数据过期与续约 2.1. 创建租约并设置 TTL(Time To Live) 创建一个 TTL 为 60 秒的租约: 该命令会返回一个租约ID,例如 2.2. 查看所有leas...","relativePath":"Tech/Middleware/etcd/etcd常见用法.md","rawContent":"# 1. 数据put和get\r\n## 1.1. 设置key和value\r\n```shell\r\netcdctl put /app \"myapp\"\r\netcdctl put /app/config/database/host \"127.0.0.1\"\r\netcdctl put /app/config/database/port \"5432\"\r\netcdctl put /app/config/api/timeout \"3000\"\r\netcdctl put /app/config/api/retries \"3\"\r\netcdctl put /app/metrics/requests_count \"100\"\r\netcdctl put /app/metrics/errors_count \"5\"\r\n```\r\n## 1.2. 获取指定key\r\n```shell\r\n# etcdctl get /app\r\n/app\r\nmyapp\r\n```\r\n## 1.3. 获取前缀下所有key\r\n```shell\r\netcdctl get --prefix --keys-only /\r\n/app\r\n/app/config/api/retries\r\n/app/config/api/timeout\r\n/app/config/database/host\r\n/app/config/database/port\r\n/app/metrics/errors_count\r\n/app/metrics/requests_count\r\n```\r\n# 2. 数据过期与续约\r\n## 2.1. 创建租约并设置 TTL(Time To Live)\r\n创建一个 TTL 为 60 秒的租约:\r\n```bash\r\netcdctl lease grant 10\r\n```\r\n 该命令会返回一个租约ID,例如 `lease ID: 694d95c322cd3623`\r\n## 2.2. 查看所有lease\r\n```shell\r\nroot@VM-0-10-ubuntu:/home/ubuntu/etcd# etcdctl lease grant 10\r\nlease 694d95c322cd3645 granted with TTL(10s)\r\nroot@VM-0-10-ubuntu:/home/ubuntu/etcd# etcdctl lease grant 20\r\nlease 694d95c322cd3647 granted with TTL(20s)\r\nroot@VM-0-10-ubuntu:/home/ubuntu/etcd# etcdctl lease list\r\nfound 2 leases\r\n694d95c322cd3645\r\n694d95c322cd3647\r\n```\r\n## 2.3. 将键与租约关联\r\n使用上述租约 ID 将键值对与租约关联\r\n```bash\r\netcdctl put /app/config/database/host \"127.0.0.1\" --lease=694d95c322cd3623\r\n```\r\n 这样,`/app/config/database/host` 这个键值对就与TTL为10秒的租约关联起来了,10秒后如果没有续约,该键值对将被自动删除。\r\n > [!note]\r\n > 1. 数据的过期时间是以lease创建的时间为准。\r\n > 2. 一个lease可以关联多个key,lease到期后,所有的key都会被删除\r\n > 3. 如果lease到期后,再关联数据会报错。Error: etcdserver: requested lease not found\r\n## 2.4. 续约租约\r\n可以在租约到期前通过以下命令续约:\r\n```bash\r\netcdctl lease keep-alive 694d95c322cd3623\r\n```\r\n该命令会重新刷新有效期,如果客户端保持连接并定期发送续约请求,租约就不会过期,与之关联的键值对也会一直存在。\r\n## 2.5. 撤销租约\r\n当需要提前删除与租约关联的键值对时,可以使用以下命令撤销租约:\r\n```bash\r\netcdctl lease revoke 694d95c322cd3623\r\n```\r\n执行该命令后,与该租约关联的所有键值对都会被删除。"},{"id":"etcd架构","title":"Etcd架构","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"etcd架构","description":"etcd 集群通过Raft算法实现了 “动态主从 + 分布式共识” 的架构,确保数据强一致性和高可用性。etcd 集群在同一时刻只能有一个主节点(Leader),且写操作必须由该主节点处理,但读操作可以从任意节点执行。etcd集群中的节点在正常运行时通过Raft算法动态选举 “领导者(Leader)...","relativePath":"Tech/Middleware/etcd/etcd架构.md","rawContent":"etcd 集群通过Raft算法实现了 **“动态主从 + 分布式共识”** 的架构,确保数据强一致性和高可用性。etcd 集群在同一时刻**只能有一个主节点(Leader)**,且**写操作必须由该主节点处理**,但读操作可以从任意节点执行。etcd集群中的节点在正常运行时通过Raft算法动态选举 “领导者(Leader)”,其他节点为 “跟随者(Follower)\r\n# 1. 节点角色与职责\r\n## 1.1. Leader 节点\r\n- 唯一负责处理客户端写请求,并将写操作日志同步到Follower节点。\r\n- 维护集群状态,协调日志复制和节点选举。\r\n## 1.2. Follower 节点\r\n- 接收并持久化 Leader 同步的日志,响应客户端读请求(或转发给 Leader)。\r\n- 在 Leader 故障时参与选举,竞争成为新 Leader。\r\n## 1.3. Candidate 节点\r\n选举过程中处于候选状态,向其他节点发送投票请求。\r\n# 2. 数据一致性与高可用性实现\r\n1. 客户端向任意节点发送写请求,节点若为Follower则转发至Leader。\r\n2. Leader 将写日志持久化后,向至少半数以上节点(如3节点集群需2个节点)发送日志复制请求。\r\n3. 当收到半数以上节点的确认后,Leader 提交日志并返回客户端成功。"},{"id":"watch使用与原理","title":"Watch使用与原理","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"watch使用与原理","description":"Watch机制是etcd的核心功能之一,它允许客户端监听指定键或前缀的变化,实现配置变更的实时通知。默认情况下,watch是从ff0000\">最新版本开始监听的。 1. 使用方式 1.1. 基本使用 添加或修改key etcd提供了多种方式使用Watch功能 1.1.1. 命令行方式 1.1.2. ...","relativePath":"Tech/Middleware/etcd/watch使用与原理.md","rawContent":"Watch机制是etcd的核心功能之一,它允许客户端监听指定键或前缀的变化,实现配置变更的实时通知。默认情况下,watch是从最新版本开始监听的。\r\n# 1. 使用方式\r\n## 1.1. 基本使用\r\n```shell\r\netcdctl watch --prefix /app\r\n# PUT\r\n# /app\r\n# myapp2\r\n# PUT\r\n# /app/owner\r\n# shinerio\r\n```\r\n添加或修改key\r\n```shell\r\netcdctl put /app myapp2\r\n# OK\r\netcdctl put /app/owner shinerio\r\n# OK\r\n```\r\netcd提供了多种方式使用Watch功能\r\n### 1.1.1. 命令行方式\r\n```shell\r\n# 监听单个键 \r\netcdctl watch mykey \r\n# 监听带有特定前缀的所有键 \r\netcdctl watch --prefix /myapp/ \r\n# 从特定历史版本开始监听 \r\netcdctl watch mykey --rev=3 \r\n# 以JSON格式输出变更 \r\netcdctl -w=json watch mykey\r\n```\r\n### 1.1.2. API调用方式\r\n Go客户端示例\r\n```go\r\n// Go客户端示例\r\nwatcher := clientv3.NewWatcher(client)\r\nwatchChan := watcher.Watch(context.Background(), \"/app\", clientv3.WithPrefix())\r\n\r\nfor watchResp := range watchChan {\r\n for _, event := range watchResp.Events {\r\n fmt.Printf(\"Type: %s Key:%s Value:%s\\n\", \r\n event.Type, event.Kv.Key, event.Kv.Value)\r\n }\r\n}\r\n```\r\n支持以下高级选项\r\n1. **指定版本**:\r\n - 从特定版本开始监听: `WithRev(revision)`\r\n - 仅获取最新事件: `WithLastRev()`\r\n2. **范围监听**:\r\n - 前缀监听: `WithPrefix()`\r\n - 范围监听: `WithRange(endKey)`\r\n3. **事件过滤**:\r\n - 仅接收PUT事件: `WithFilterPut()`\r\n - 仅接收DELETE事件: `WithFilterDelete()`\r\n4. **进度通知**:\r\n - 当没有事件时发送进度更新: `WithProgressNotify()`\r\n - 用于确认连接活跃性\r\n# 2. 工作原理\r\n## 2.1. 整体架构\r\nWatch机制的核心组件包括:\r\n1. **watchableStore**: 可监听的存储层\r\n2. **watcherGroup**: 监听器群组管理\r\n3. **watcher**: 单个监听器\r\n4. **eventBuffer**: 事件缓冲区\r\n## 2.2. 事件流转过程\r\n1. **客户端发起Watch请求**:\r\n - 指定key/范围、起始版本等参数\r\n - 建立长连接(通常基于gRPC流)\r\n2. **服务端注册watcher**:\r\n - 创建watcher对象\r\n - 将watcher添加到watcherGroup中\r\n - 根据起始版本决定是否需要从历史事件开始发送\r\n3. **同步历史事件**:\r\n\t- 如果客户端指定了过去的版本号,服务端会从MVCC存储中查询该版本到当前的所有历史事件,将这些事件发送给客户端。\r\n4. **异步事件通知**:\r\n - 当etcd处理写请求时,会同时:\r\n - 应用到存储层\r\n - 通过触发器(trigger)通知watchableStore\r\n - watchableStore将事件分发给匹配的watcher\r\n - 通过长连接将事件推送给客户端\r\n## 2.3. MVCC的重要作用\r\nWatch机制严重依赖etcd的MVCC(多版本并发控制)功能:\r\n1. 每个修改操作都会生成新的版本号(revision)\r\n2. 历史版本使得Watch可以从任意点\"回溯\"监听\r\n3. 由于保留了历史版本,即使客户端断开重连,也不会错过事件\r\n## 2.4. 内存和性能优化\r\n为了高效处理大量Watch请求,etcd采用了多种优化:\r\n1. **批处理机制**:\r\n - 多个事件会被批量发送给客户端\r\n - 减少网络往返次数\r\n2. **压缩与淘汰**:\r\n - 基于时间/版本的自动压缩\r\n - 设置内存上限,超出时淘汰不活跃的watcher\r\n3. **按需机制**:\r\n - 服务端维护未发送的事件队列\r\n - 客户端ready时才发送\r\n4. **进度通知**:\r\n - 定期发送当前进度,避免长时间无事件导致连接失效\r\n## 2.5. 可靠性保障\r\netcd Watch提供高可靠性保证:\r\n1. **断线重连**:\r\n - 客户端断开连接后可从上次接收到的版本继续监听\r\n - 不会丢失中间发生的事件\r\n2. **版本保证**:\r\n - 只要历史版本未被压缩,客户端总能收到完整事件序列\r\n - 如果请求的版本已被压缩,服务端会返回压缩错误\r\n3. **集群支持**:\r\n - Watch请求可发送到任何集群成员\r\n - Leader变更不影响Watch功能\r\n# 3. 常见挑战和解决方案\r\n1. **历史事件压缩**:\r\n - 问题:如果监听的**起始版本已被压缩,将无法获取完整事件**\r\n - 解决:合理设置压缩策略,或实现备用同步机制\r\n2. **大量watcher的性能**:\r\n - 问题:大量watcher可能导致内存压力和事件分发延迟\r\n - 解决:使用前缀合并,减少watcher数量;合理设置服务端资源限制\r\n3. **网络中断处理**:\r\n - 问题:长时间网络中断可能丢失事件\r\n - 解决:实现健壮的重连逻辑,记录最后处理的版本号"},{"id":"数据存储","title":"数据存储","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":13,"slug":"数据存储","description":"1. 数据存储格式 etcd的数据存储格式主要是基于键值对的形式。 - 键:是一个唯一的字符串,用于标识存储的数据,类似于字典中的键。例如,在一个配置管理系统中,键可能是某个服务的名称加上配置项的名称,如。 - 值:可以是任意类型的数据,如字符串、整数、JSON 对象等,这取决于具体的应用场景。例如...","relativePath":"Tech/Middleware/etcd/数据存储.md","rawContent":"# 1. 数据存储格式\r\netcd的数据存储格式主要是基于**键值对**的形式。\r\n- **键**:是一个唯一的字符串,用于标识存储的数据,类似于字典中的键。例如,在一个配置管理系统中,键可能是某个服务的名称加上配置项的名称,如`service1.timeout`。\r\n- **值**:可以是任意类型的数据,如字符串、整数、JSON 对象等,这取决于具体的应用场景。例如,对于上述`service1.timeout`这个键,其对应的值可能是一个整数`3000`,表示服务1的超时时间为 3000 毫秒;也可能是一个JSON 对象,包含更复杂的配置信息。\r\netcd在底层将这些键值对数据以**B+树**的结构进行存储,这种数据结构可以提供高效的查找、插入和删除操作。同时,etcd还使用了**Raft 算法**来保证数据在分布式环境下的一致性和可靠性,确保每个节点都能保持相同的键值对数据副本。\r\n\r\netcd中的B+树数据既存储在内存中,也存储在磁盘上,采用了内存与磁盘相结合的存储方式:\r\n- **内存存储**:etcd会将一部分经常访问的数据,以B+树的结构缓存在内存中,以加快数据的访问速度。这样,对于频繁读取的键值对,就可以直接从内存中获取,避免了磁盘I/O操作,从而提高系统的性能和响应速度。\r\n- **磁盘存储**:etcd会把数据持久化存储到磁盘上,以保证数据的可靠性和持久性。在磁盘上,数据以一定的格式组织成文件进行存储,其中就包含了B+树结构相关的数据。当etcd启动时,会从磁盘中加载数据到内存中,恢复B+树的状态。\r\n# 2. 数据读取\r\netcd获取key(读操作)主要是从内存中完成的,而非直接从磁盘读取。这是etcd高性能读取操作的关键设计。\r\n1. etcd将所有活跃的键值对数据保存在内存中,通常使用B树或B+树的变种结构\r\n2. 读操作直接从这些内存数据结构中获取数据,无需访问磁盘\r\n3. 内存中不仅存储最新版本,还保留了历史版本,这支持了etcd的事务和历史版本查询功能\r\n> [!note]\r\n> 由于etcd会在内存中存放所有数据,因此不建议使用etcd作为持久化数据库,存储大量用户数据。建议etcd用于存放元数据或配置类数据。\r\n## 2.1. 内存控制的办法\r\netcd的MVCC(多版本并发控制)机制会为每次修改创建新版本,保留旧版本。这会导致:\r\n1. **内存持续增长**:\r\n - 每次PUT/DELETE操作都会创建新的版本,而不是覆盖旧版本\r\n - 历史版本会占用额外内存,且随操作频率线性增长\r\n2. **无限增长问题**:\r\n - 如不进行版本清理,即使是对同一个key反复修改,也会产生大量版本\r\n - 理论上内存使用量会无限增长,最终导致OOM为解决这个问题,etcd提供了几种控制历史版本的机制:\r\n### 2.1.1. 压缩(Compaction)\r\netcd的关键机制,用于清理历史版本,压缩后,指定版本之前的历史记录将被删除\r\n支持两种模式:\r\n- 基于版本号: `etcdctl compact 5`(清理revision 5之前的所有版本)\r\n- 基于时间: 清理特定时间点之前的版本\r\n配置etcd定期自动执行压缩,常用配置选项:\r\n- `--auto-compaction-retention='1h'`: 保留最近1小时的历史\r\n- `--auto-compaction-mode='periodic'`: 基于时间的周期压缩\r\n- `--auto-compaction-mode='revision'`: 基于版本号的压缩\r\n### 2.1.2. 租约机制(Lease)\r\n- 为key设置TTL(生存时间)\r\n- 过期后自动删除key及其所有版本\r\n# 3. 磁盘存储格式\r\n默认情况下,etcd将数据存储在`/var/lib/etcd`目录下(可通过启动参数`--data-dir`指定其他位置)。etcd的持久化则使用预写式日志(WAL:Write Ahead Log)进行记录存储。在WAL的体系中,所有的数据在提交之前都会进行日志记录。在etcd的持久化存储目录中,有两个子目录。\r\n- 一个是WAL,存储着所有事务的变化记录\r\n- 另一个则是snapshot,用于存储某一个时刻etcd所有目录的数据\r\n```shell\r\n# tree /home/ubuntu/data/\r\n/home/ubuntu/data/\r\n└── member\r\n ├── snap\r\n │ └── db\r\n └── wal\r\n ├── 0000000000000000-0000000000000000.wal\r\n └── 0.tmp\r\n\r\n4 directories, 3 files\r\n```\r\n## 3.1. WAL\r\nWAL是etcd数据持久化的主要机制,它遵循\"先写日志,后应用\"的原则。\r\n### 3.1.1. 主要特点\r\n1. **追加写入**:所有的写操作都以追加的方式写入WAL文件,确保写入效率\r\n2. **顺序性**:记录严格按时间顺序存储\r\n3. **原子性**:每个条目要么完全写入,要么完全不写入\r\n### 3.1.2. 包含的主要信息\r\n1. **Entry类型记录**:包含Raft协议的操作如提案(proposals)\r\n2. **State类型记录**:保存Raft状态信息\r\n3. **CRC校验**:每个记录都有校验和,确保数据完整性\r\n4. **元数据**:如term, index等Raft相关信息\r\n### 3.1.3. WAL分段管理\r\netcd将WAL文件按大小分段(默认64MB),形成一系列有序的WAL文件,命名格式通常为`[序号]-[第一个索引].wal`。\r\n## 3.2. Snapshot(快照)\r\n随着系统运行,WAL文件会不断增长。为了避免在重启时重放过多的WAL条目,etcd会周期性创建状态快照。\r\n### 3.2.1. 特点\r\n1. **完整状态**:包含某一时刻etcd的完整状态\r\n2. **压缩格式**:通常是经过压缩的,减少存储空间\r\n3. **定期触发**:可基于时间或WAL大小触发\r\n### 3.2.2. Snapshot内容\r\n1. **键值数据**:存储区中的所有键值对\r\n2. **Raft元数据**:如最后应用的日志索引等\r\n3. **版本信息**:MVCC版本数据\r\n## 3.3. WAL与Snapshot的协作机制\r\nWAL和Snapshot协同工作,共同保障etcd的数据一致性和性能:\r\n### 3.3.1. 写入流程\r\n- 所有修改操作首先写入WAL\r\n- 成功写入WAL后,修改应用到内存中的状态机\r\n### 3.3.2. 快照触发\r\n- 当WAL累积到一定大小或经过一定时间后\r\n - 将当前状态序列化为快照文件\r\n- 记录快照对应的最后应用的日志索引\r\n### 3.3.3. WAL清理\r\n- 创建快照后,系统清理已被快照包含的WAL段\r\n- 保留最近的几个快照作为冗余备份\r\n### 3.3.4. 恢复流程\r\n当etcd节点重启时:\r\n1. 首先加载最新的快照文件,恢复到快照时刻的状态\r\n2. 然后从快照的末尾索引开始,重放后续WAL记录\r\n3. 完成状态恢复后,etcd开始正常服务\r\n### 3.3.5. 协作示例\r\n假设etcd具有以下文件:\r\n- `0000000000000000-0000000000000000.wal`\r\n- `0000000000000001-0000000000010000.wal`\r\n- `0000000000000002-0000000000020000.wal`\r\n- `snapshot.db` (包含索引15000的状态)\r\n重启时,etcd会:\r\n1. 加载`snapshot.db`恢复到索引15000\r\n2. 从索引15001开始重放第二个WAL文件的后半部分和第三个WAL文件\r\n3. 重建完整的内存状态\r\n### 3.3.6. 特殊设计考量\r\n1. **预写缓冲区**:WAL写入前会使用内存缓冲区提高性能\r\n2. **fsync策略**:可配置同步策略,平衡性能和可靠性\r\n3. **压缩算法**:快照通常使用压缩算法减少磁盘使用\r\n4. **版本控制**:与MVCC机制结合,支持多版本并发控制\r\n这种设计使etcd能够在保证数据安全的同时,提供较高的性能,适合作为分布式系统的协调服务和配置中心。\r\n# 4. 数据版本\r\netcd支持数据的版本控制,每个键值对都有一个对应的版本号。当数据发生更新时,版本号会自动递增,这使得用户可以方便地进行数据的回溯和查看历史版本。\r\n> [!note]\r\n> etcd使用的是64位整数(int64)来存储全局版本号。对于int64类型,其最大值约为9.2×10^18。假设etcd每秒处理1百万个写操作(这已经是非常高的吞吐量),理论上需要大约292,471年才会发生溢出,因此在实际应用中这个问题几乎不需要担心。\r\n## 4.1. 原理\r\n在 etcd 中,`create_revision`、`mod_revision` 和 `version` 是与键值对数据相关的元数据字段,用于记录数据的版本和修改历史信息。\r\n- **`create_revision`**:表示键值对创建时的修订版本号。每当一个新的键值对被创建时,etcd 会为其分配一个唯一的修订版本号,这个版本号会随着 etcd 中事务操作的执行而单调递增。通过 `create_revision` 可以准确地知道某个键值对是在 etcd 的哪个修订版本中被创建的。\r\n- **`mod_revision`**:代表键值对最后一次修改时的修订版本号。当键值对的 value 被更新或者键被删除时,`mod_revision` 的值会更新为当前的修订版本号。所以 `mod_revision` 总是反映键值对最新的修改状态对应的修订版本。\r\n- **`version`**:表示键值对的版本号。它记录了该键值对被修改的次数,从创建开始计数,每次对键值对进行修改(包括更新值或删除后重新创建),`version` 都会递增。与 `create_revision` 和 `mod_revision` 不同,`version` 是一个相对的、针对特定键的版本计数,而不是 etcd 全局的修订版本号。\r\n> [!note]\r\n创建一个键值对 `/app/config/timeout`,其 `create_revision` 可能是100,`mod_revision` 也是100,`version` 为 1。如果对该键值对进行了一次更新操作,那么 `mod_revision` 会更新为一个更大的修订版本号,比如 105,`version` 会递增为 2,而 `create_revision` 保持不变仍为100。这些字段有助于实现 etcd 的多版本控制功能,方便用户查询和管理数据的不同版本,以及进行事务处理和一致性检查等操作。\r\n## 4.2. 示例\r\n```shell\r\netcdctl put /app/name myapp1\r\n# OK\r\netcdctl get /app/name -w=json\r\n# {\"header\":{\"cluster_id\":14841639068965178418,\"member_id\":10276657743932975437,\"revision\":36,\"raft_term\":2},\"kvs\":[{\"key\":\"L2FwcC9uYW1l\",\"create_revision\":36,\"mod_revision\":36,\"version\":1,\"value\":\"bXlhcHAx\"}],\"count\":1}\r\netcdctl put /app/name myapp2\r\n# OK\r\netcdctl get /app/name -w=json\r\n{\"header\":{\"cluster_id\":14841639068965178418,\"member_id\":10276657743932975437,\"revision\":37,\"raft_term\":2},\"kvs\":[{\"key\":\"L2FwcC9uYW1l\",\"create_revision\":36,\"mod_revision\":37,\"version\":2,\"value\":\"bXlhcHAy\"}],\"count\":1}\r\netcdctl get /app/name --rev=37\r\n# /app/name\r\n# myapp2\r\netcdctl get /app/name --rev=36 # 查看之前版本的数据\r\n# /app/name\r\n# myapp1\r\netcdctl del /app/name\r\n# 1\r\netcdctl get /app/name --rev=36 # 查看删除前的数据\r\n# /app/name\r\n# myapp1\r\n```"},{"id":"kafka stream","title":"Kafka Stream","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":18,"slug":"kafka-stream","description":"1. 流式计算和批计算 1.1. 流式计算 流式计算是持续地从数据源获取数据,并实时地对数据进行处理和分析。 1.1.1. 特点 - 实时性强,能够在数据到达时立即进行处理。 - 数据无边界,是持续产生的 - 计算模型有状态,需要维护中间状态,一般用增量计算代替全量计算 - 时延敏感,需要低延迟处理...","relativePath":"Tech/Middleware/MessageQueue/kafka stream.md","rawContent":"# 1. 流式计算和批计算\r\n## 1.1. 流式计算\r\n流式计算是持续地从数据源获取数据,并实时地对数据进行处理和分析。\r\n### 1.1.1. 特点\r\n- **实时性强**,能够在数据到达时立即进行处理。\r\n- **数据无边界**,是持续产生的\r\n- **计算模型有状态**,需要维护中间状态,一般用增量计算代替全量计算\r\n- **时延敏感**,需要低延迟处理。\r\n### 1.1.2. 应用场景\r\n- 实时监控和异常检测(如网络流量监控、安全威胁检测等)\r\n- 实时数据分析(如实时点击流分析、用户行为分析等)\r\n- 实时决策和推荐(如实时定价、个性化推荐等)\r\n## 1.2. 批计算\r\n以离线的方式,周期性地从数据源根据过滤条件获取数据集,并对整个数据集进行处理和分析。\r\n### 1.2.1. 特点\r\n- **数据集有边界**,一般是静态的\r\n- **计算模型无状态**,不需要维护中间状态,一般采用全量计算\r\n- **延迟不敏感**,但处理吞吐量大\r\n### 1.2.2. 应用场景\r\n- 离线数据分析(如用户画像)\r\n- 大规模数据处理(如网页索引、推荐系统构建等)\r\n- 机器学习模型训练和评估\r\n# 2. kafka stream\r\n常见的流式处理框架有flink和storm等,相比如这些专业的流失处理框架,kafka stream的一个最大优点是非常轻量,kafka Streams利用kafka作为数据源,其本身仅仅是kafka的一个客户端库,它可以像其他普通的Kafka消费者客户端一样,通过标准的kafka协议与broker进行交互。kafka streams实时消费上游的topic,经过自定义的计算,将结果生产到下游的topic,理论上你可以自己写调用Producer api和Consumer api来实现一个流计算应用。\r\n## 2.1. 关键特性\r\n- 时间的定义:生成时间、处理时间、事件时间\r\n- 时间窗口:sliding window、hopping window、event window\r\n- 处理晚到数据\r\n- map-reduce\r\n- table join\r\n- 中间状态\r\n- exactly-once\r\n## 2.2. 计算模型\r\n`KStream`和`KTable`则是`Kafka Stream`库在客户端对数据流进行各种转换操作时使用的抽象模型 \r\n- `KStream`代表了一个无界的持续更新的记录流,用于表示和处理有序的、不断插入的事件序列\r\n- `KTable`是持久化状态存储,用于表示记录对连续数据流执行计算后持久化存储的结果视图\r\n## 2.3. 存储模型\r\n### 2.3.1. KTable\r\nKafka Stream可以为每个流任务嵌入一个或多个本地状态存储[KTable](https://kafka.apache.org/31/documentation/streams/architecture#streams_architecture_recovery),并且允许开发者通过API的方式进行访问、查询所需要处理的数据。这些状态数据底层默认是基于RocksDB数据库实现的,本质其实是在内存的一个hashmap。 而且状态存储默认支持同步到远端的kafka broker,以topic的方式记录本地存储的changelog。 当kafka streams的task因重建或者动态扩缩容而发生迁移时,若迁移后的kafka streams实例无法从本地磁盘恢复状态,就会读取下changelog的topic来重建状态存储。具体的工作流程如下:\r\n1. **从变更日志恢复状态**: 当Kafka Streams应用启动时,它首先检查本地状态存储。如果状态存储存在,它会检查最后处理的偏移量。\r\n - 如果本地状态存储的数据是完整的,Kafka Streams会从本地状态存储恢复数据。\r\n - 然后,从变更日志主题中读取从最后一个处理偏移量到当前最新偏移量之间的所有变更,并将这些变更应用到本地状态存储。\r\n2. **从检查点恢复进度**: Kafka Streams还会从检查点恢复处理进度。检查点记录了每个分区的偏移量,即每个分区处理到哪一条记录。当应用程序重新启动时,它会从这些偏移量继续读取和处理记录。\r\n3. **保证数据一致性**:\r\n - Kafka Streams确保本地状态存储与变更日志主题中的数据一致。每次状态存储更新时,都会记录在变更日志中。\r\n - 通过事务机制,Kafka Streams可以确保所有操作(读取、处理、写入)的一致性。即使在发生故障时,未完成的事务也会回滚,保证不会出现重复处理或数据丢失的情况。\r\n### 2.3.2. GlobalKtable\r\n`KTable`的数据是按主题分区的,每个流处理应用实例只处理其负责的partition的数据。`globalKtable`保存来自所有kafka stream client输出的ktable数据,相同key的情况下,kafka的顺序机制保证后写入的会覆盖之前写入的。\r\n- **数据分布**:\r\n - `KTable`:按分区处理,每个实例只处理分配给它的分区数据。\r\n - `GlobalKTable`:全局处理,每个实例都有整个表的副本。\r\n- **状态存储**:\r\n - `KTable`:本地状态存储与分区数据相关。\r\n - `GlobalKTable`:本地状态存储整个表的数据。\r\n- **查询能力**:\r\n - `KTable`:只能查询本地分区数据。\r\n - `GlobalKTable`:可以查询整个表的数据。\r\n- **使用场景**:\r\n - `KTable`:适用于需要对分区数据进行处理和变换的场景。\r\n - `GlobalKTable`:适用于需要全局状态访问的场景。\r\n> 优势globalKtable的数据在本地也会缓存,因此无法避免并发问题,并发场景下建议将数据存储到关系型数据库或redis等集中式仓库。\r\n\r\n[kafka streams:一款轻量级流式计算引擎 | 大飞哥的博客 (hustclf.github.io)](https://hustclf.github.io/posts/cfee7ffb/)\r\n[KafkaStream之时间窗口WindowBy_kafka steam windowedby 时间不准确-CSDN博客](https://blog.csdn.net/u012364631/article/details/94019707)\r\n\r\n## 2.4. 任务分配\r\n由于kafka stream的数据来源是topic,因此kafka stream任务的分配也和partition的分配一样,kafka会将多个partition平均分配到多个节点上,如果kafka stream client的数量大于kafka stream partition,那么会存在某个client没有任务可以处理的情况。当kafka stream client动态增加会减少时,会出现tasks重新分配。tasks重新分配需要`state store`重建然后才能继续执行task, 重建`state store`会优先从本地磁盘恢复,若找不到本地磁盘信息则会通过远端kafka集群的备份topic来重建这些信息,可能会耗时较长。\r\n## 2.5. 有向无环图\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406222050375.png)\r\nSystem.out.println(topology.describe());topology 中 processor 有两种比较特殊。\r\n- source processor: 定义了 topology 的入口,即从哪些topic 读取数据 \r\n- sink processor: 定义了 topology 的出口,即将最终结果写入哪些 topic\r\n\r\n```java\r\npublic class App { \r\n public static void main(String[] args) throws InterruptedException { \r\n Properties properties = new Properties(); \r\n properties.put(StreamsConfig.APPLICATION_ID_CONFIG, \"wordcount-app\"); \r\n properties.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, \"localhost:9092\"); \r\n properties.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); \r\n properties.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); \r\n \r\n Topology topology = new Topology(); \r\n StoreBuilder> countStoreBuilder = \r\n Stores.keyValueStoreBuilder( \r\n Stores.persistentKeyValueStore(\"counts-store\"), \r\n Serdes.String(), \r\n Serdes.Long() \r\n ); \r\n // 将状态存储添加到拓扑 \r\n topology.addStateStore(countStoreBuilder); \r\n \r\n // 使用text-lines-topic作为输入源,kafka stream支持定义从多个数据源获取数据\r\n topology.addSource(\"Source\", \"text-lines-topic\") \r\n // processor1: 删除引号 \r\n .addProcessor(\"removeQuotes\", RemoveQuotesProcessor::new,\"Source\") \r\n // processor2: 单词统计 \r\n .addProcessor(\"wordCount\", WordCountProcessor::new,\"removeQuotes\") // wordCount需要用到单词计数作为中间状态\r\n .connectProcessorAndStateStores(\"wordCount\", \"counts-store\") \r\n // 将数据输出到streams-wordcount-output中,支持多个topic输出,输出的value是long类型,所以需要指定为LongSerializer()\r\n .addSink(\"Sink\", \"streams-wordcount-output\", new StringSerializer(), new LongSerializer(), \"wordCount\"); \r\n System.out.println(topology.describe()); \r\n \r\n KafkaStreams kafkaStreams = new KafkaStreams(topology, properties); \r\n kafkaStreams.setUncaughtExceptionHandler((Thread thread, Throwable throwable) -> { \r\n System.err.println(\"Uncaught exception in thread \" + thread + \": \" + throwable); \r\n throwable.printStackTrace(); \r\n }); \r\n \r\n kafkaStreams.setStateListener((newState, oldState) -> { \r\n System.out.println(\"State changed from \" + oldState + \" to \" + newState); \r\n }); \r\n \r\n kafkaStreams.start(); \r\n } \r\n \r\n static class RemoveQuotesProcessor implements Processor { \r\n \r\n /** \r\n * 泛型1:当前processor处理后输出的key的类型 \r\n * 泛型2:当前processor处理后输出的value的类型 \r\n */ \r\n private ProcessorContext context; \r\n \r\n /** \r\n * 只调用一次,在处理器开始处理数据之前被调用,用于初始化处理器。 \r\n * 1. 获取和存储 ProcessorContext。 \r\n * 2. 初始化任何需要的资源,如状态存储。 \r\n * 3. 设置定时器或调度定期操作 \r\n */ \r\n @Override \r\n public void init(ProcessorContext context) { \r\n this.context = context; \r\n } \r\n \r\n @Override \r\n public void process(Record record) { \r\n String replace = record.value().replace(\"\\\"\", \"\"); \r\n // 创建新的记录,并保留原始记录的时间戳 \r\n context.forward(record.withValue(replace)); \r\n } \r\n \r\n @Override \r\n public void close() { \r\n Processor.super.close(); \r\n } \r\n } \r\n \r\n static class WordCountProcessor implements Processor { \r\n \r\n private ProcessorContext context; \r\n private KeyValueStore kvStore; \r\n \r\n @Override \r\n public void init(ProcessorContext context) { \r\n this.context = context; \r\n // 初始化计数器,用于存储中间计算状态,以便进行流式计算\r\n this.kvStore = context.getStateStore(\"counts-store\"); \r\n } \r\n \r\n @Override \r\n public void process(Record record) { \r\n String[] words = record.value().toLowerCase().split(\"\\\\W+\"); \r\n for (String word : words) { \r\n Long count = kvStore.get(word); \r\n if (count == null) { \r\n kvStore.put(word, 1L); \r\n context.forward(new Record<>(word, 1L, record.timestamp())); \r\n } else { \r\n kvStore.put(word, count + 1); \r\n context.forward(new Record<>(word, count + 1, record.timestamp())); \r\n } \r\n } \r\n } \r\n } \r\n}\r\n```\r\n\r\n打印出来的topology如下:\r\n```\r\nTopologies:\r\n Sub-topology: 0\r\n Source: Source (topics: [text-lines-topic])\r\n --> removeQuotes\r\n Processor: removeQuotes (stores: [])\r\n --> wordCount\r\n <-- Source\r\n Processor: wordCount (stores: [counts-store])\r\n --> Sink\r\n <-- removeQuotes\r\n Sink: Sink (topic: streams-wordcount-output)\r\n <-- wordCount\r\n```\r\n\r\n## 2.6. 时间\r\ntime是kafka streams中一个非常重要的概念,下文基于窗口的处理操作也依赖时间定义来划分边界。kakfa streams分为3种时间定义:\r\n- Event time: 事件真实发生的时间,在写入kafka时可以指定 timestamp来表述该时间。如果事件是汽车GPS传感器报告的地理位置变化,则相关的事件时间就是GPS传感器捕获位置变化的时间。\r\n- Processing time:日志在kafka streams任务中处理时的时间,即记录被消费的时间。处理时间可能比原始事件时间晚几毫秒、几小时或几天等。想象一个分析平台,它读取和处理来自汽车传感器的地理位置数据,并将其呈现在车队管理仪表板上。在这里,分析应用程序的处理时间可能比事件时间晚几毫秒或几秒(例如,基于Apache Kafka和Kafka Streams的实时管道),或者晚几小时(例如,基于Apache Hadoop或Apache Spark的批处理管道)。\r\n- Ingestion time:记录日志被写进kafka的topic的时间。与事件时间的区别在于,这个摄入时间戳是在Kafka broker将记录追加到目标topic时生成的,而不是在\"源头\"创建记录时生成的。与处理时间的区别在于,处理时间是流处理应用程序处理记录的时间。例如,如果一个记录从未被处理,它就没有处理时间的概念,但它仍然有摄入时间。\r\n事件时间和摄入时间之间的选择实际上是通过Kafka的配置来完成的(而不是Kafka Streams)。从Kafka 0.10.x开始,时间戳会自动嵌入到Kafka消息中。根据Kafka的配置,这些时间戳代表事件时间或摄入时间。相应的Kafka配置设置可以在broker级别或每个topic上指定。Kafka Streams中的默认时间戳提取器将按原样检索这些嵌入的时间戳。因此,应用程序的有效时间语义取决于这些嵌入时间戳的Kafka配置。\r\n\r\nKafka Streams通过TimestampExtractor接口为每个数据记录分配一个时间戳。每条记录的时间戳描述了流在时间方面的进展,并被诸如窗口操作等依赖时间的操作所使用。因此,这个时间只有在新记录到达处理器时才会前进。我们将这种数据驱动的时间称为应用程序的stream time,以区别于应用程序实际执行时的wall-clock time。TimestampExtractor接口的具体实现将为stream time定义提供不同的语义。例如,基于数据记录的实际内容(如嵌入的时间戳字段)检索或计算时间戳,以提供事件时间语义,而返回当前wall-clock time则为stream time提供处理时间语义。因此,开发人员可以根据其业务需求强制执行不同的时间概念。\r\n\r\n最后,当Kafka Streams应用程序向Kafka写入记录时,它也会为这些新记录分配时间戳。时间戳的分配方式取决于上下文:\r\n1. 当通过处理某些输入记录生成新的输出记录时,例如在process()函数调用中触发的context.forward(),输出记录时间戳直接继承自输入记录时间戳。\r\n2. 当通过周期性函数(如Punctuator#punctuate())生成新的输出记录时,输出记录时间戳被定义为流任务的当前内部时间(通过context.timestamp()获得)。\r\n3. 对于聚合操作,结果更新记录的时间戳将是所有贡献到结果的输入记录的最大时间戳。\r\n也可以在Processor API中通过在调用`#forward()`时显式地为输出记录分配时间戳来改变默认行为。\r\n\r\n对于聚合和连接操作,时间戳的计算遵循以下规则:\r\n1. 对于具有左右输入记录的连接操作(stream-stream, table-table),输出记录的时间戳被分配为max(left.ts, right.ts)。\r\n2. 对于stream-table连接,输出记录被分配来自流记录的时间戳。\r\n3. 对于聚合操作,Kafka Streams还会计算所有记录的最大时间戳,按键计算,可以是全局的(对于非窗口化操作)或每个窗口的。\r\n4. 对于无状态操作,输入记录时间戳被直接传递。对于flatMap及其兄弟操作(发出多个记录),所有输出记录都继承自相应输入记录的时间戳。\r\n## 2.7. 窗口\r\nkafka Streams窗口默认是键控窗口(每个key有独立窗口维护)。在Kafka Streams中,时间窗口操作通常是在分组(grouped)或者已分组(keyed)的流上进行的,每个不同的 key 都有其独立的时间窗口集合。这意味着相同key的消息会被聚合到同一个窗口中,而不同key的消息则分别进入各自的窗口。\r\n好处:\r\n- 这种设计允许并行处理不同 key 的数据。\r\n- 可以独立地对每个 key 进行聚合、计数或其他窗口操作。\r\n假设您在处理用户点击数据,key 是用户 ID。每个用户(每个 key)都会有自己的一组时间窗口,用于统计该用户在不同时间段的点击次数。我们也可以通过一些方案可以实现非键控窗口(全局窗口),例如:\r\n```java\r\nKStream input = ...;\r\n\r\nKTable, Long> windowedCounts = input\r\n .selectKey((k, v) -> \"global\") // 将所有消息分配到同一个 key\r\n .groupByKey()\r\n .windowedBy(TimeWindows.of(Duration.ofMinutes(5)))\r\n .count();\r\n```\r\n\r\nkafka streams的DSL提供了多种[时间窗口](https://kafka.apache.org/31/documentation/streams/developer-guide/dsl-api#sliding-time-windows)\r\n\r\n| 窗口类型 | 表现 | 描述 |\r\n| -------------------- | --------- | ----------------------- |\r\n| Hopping time window | 基于时间 | 大小固定、重叠的窗口 |\r\n| Tumbling time window | 基于时间 | 大小固定、不重叠的窗口 |\r\n| Sliding time window | 基于时间 | 大小固定,重叠的窗口,仅用于join计算的窗口 |\r\n| Session window | 基于session | 大小不固定、不重叠的、基于数据驱动的窗口 |\r\n| | | |\r\n### 2.7.1. hopping time window\r\n定义一个大小为5分钟、间隔为1分钟的Hopping time window。kafka窗口采用左闭右开原则,即窗口是`[0,5),[5,10),[10,15)....`\r\n```java\r\n// A hopping time window with a size of 5 minutes and an advance interval of 1 minute. \r\nDuration windowSize = Duration.ofMinutes(5); \r\nDuration advance = Duration.ofMinutes(1); \r\nTimeWindows.ofSizeWithNoGrace(windowSize).advanceBy(advance);\r\n```\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406262317017.png)\r\n### 2.7.2. Tumbling time window\r\nTumbling time window是hopping time window的一种特例,即windows和interval一样大。这种窗口相互之间不会重叠,可以保证一个记录只会属于一个窗口。kafka窗口采用左闭右开原则,定义一个大小为5分钟的Tumbling time window,`[0,5),[5,10),[10,15)....`\r\n```java\r\n// A tumbling time window with a size of 5 minutes (and, by definition, an implicit \r\n// advance interval of 5 minutes), and grace period of 1 minute. \r\nDuration windowSize = Duration.ofMinutes(5); \r\nDuration gracePeriod = Duration.ofMinutes(1); \r\nTimeWindows.ofSizeAndGrace(windowSize, gracePeriod); \r\n \r\n// The above is equivalent to the following code: \r\nTimeWindows.ofSizeAndGrace(windowSize, gracePeriod).advanceBy(windowSize);\r\n```\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406262304468.png)\r\nThe grace period to admit out-of-order events to a window. Must be non-negative. 是一个窗口在其原定结束时间后,额外等待迟到数据的时间段。它允许在窗口结束后的一段时间内继续接受和处理迟到的数据,而不会立即关闭窗口。\r\n\r\n以下代码,每隔10秒对当前窗口内的单词数量进行计数。\r\n```java\r\npublic static void main(String[] args) throws InterruptedException { \r\n Properties properties = new Properties(); \r\n properties.put(StreamsConfig.APPLICATION_ID_CONFIG, \"wordcount-app\"); \r\n properties.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, \"localhost:9092\"); \r\n properties.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); \r\n properties.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); \r\n \r\n StreamsBuilder builder = new StreamsBuilder(); \r\n builder.stream(\"text-lines-topic\", Consumed.with(Serdes.String(), Serdes.String())) \r\n .map((key, value) -> new KeyValue<>(key, value.replace(\"\\\"\", \"\"))) \r\n .flatMapValues((key, textLine) -> Arrays.asList(textLine.toLowerCase().split(\"\\\\W+\"))) \r\n .groupBy((key, word) -> word) \r\n .windowedBy(TimeWindows.ofSizeAndGrace(Duration.ofSeconds(10), Duration.ofSeconds(2))) \r\n .count() \r\n .toStream() \r\n .map((key, value) -> new KeyValue<>(key.key(), value)) \r\n .to(\"streams-wordcount-output\", Produced.with(Serdes.String(), Serdes.Long())); \r\n \r\n KafkaStreams kafkaStreams = new KafkaStreams(builder.build(), properties); \r\n kafkaStreams.setUncaughtExceptionHandler((Thread thread, Throwable throwable) -> { \r\n System.err.println(\"Uncaught exception in thread \" + thread + \": \" + throwable); \r\n throwable.printStackTrace(); \r\n }); \r\n \r\n kafkaStreams.setStateListener((newState, oldState) -> { \r\n System.out.println(\"State changed from \" + oldState + \" to \" + newState); \r\n }); \r\n \r\n kafkaStreams.start(); \r\n}\r\n```\r\n### 2.7.3. slide time window\r\n\r\n# 3. docker安装kafka测试\r\n[Apache Kafka](https://kafka.apache.org/31/documentation/streams/quickstart)\r\n```shell\r\n# zookeeper\r\ndocker run -d --name zookeeper -p 2181:2181 -t wurstmeister/zookeeper\r\n# kafka, docker inspect查看zookeeper地址并配置\r\ndocker run -d --name kafka -p 9092:9092 -e KAFKA_BROKER_ID=0 -e KAFKA_ZOOKEEPER_CONNECT=172.17.0.2:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 wurstmeister/kafka\r\n```\r\n进入kafka容器后,创建topic\r\n```shell\r\n/opt/kafka_2.13-2.8.1/bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic text-lines-topic --partitions 1 --replication-factor 1\r\n```\r\n运行以下代码,进行流式计算,单词统计\r\n```java\r\npublic class App { \r\n public static void main(String[] args) { \r\n Properties properties = new Properties(); \r\n properties.put(StreamsConfig.APPLICATION_ID_CONFIG, \"wordcount-app\"); \r\n properties.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, \"localhost:9092\"); \r\n properties.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); \r\n properties.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); \r\n \r\n StreamsBuilder builder = new StreamsBuilder(); \r\n builder \r\n // 使用topic text-lines-topic作为输入源 \r\n .stream(\"text-lines-topic\") \r\n // 将每个小时转换为单词列表 \r\n\t .flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split(\"\\\\W+\"))) \r\n // 按照单词分组 \r\n .groupBy((key, word) -> word) \r\n // 1. 对每个单词计数 \r\n // 2. 将KTable持久化到一个名为\"counts-store\"的状态存储中(默认使用RocksDB) \r\n .count(Materialized.as(\"counts-store\")) \r\n // 将技术结果输出到topic streams-wordcount-output中 \r\n .toStream().to(\"streams-wordcount-output\", Produced.with(Serdes.String(), Serdes.Long())); \r\n \r\n Topology topology = builder.build(); \r\n KafkaStreams streams = new KafkaStreams(topology, properties); \r\n streams.start(); \r\n } \r\n}\r\n```\r\n生产消息\r\n```shell\r\n/opt/kafka_2.13-2.8.1/bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic text-lines-topic\r\n>good good study\r\n>day day up\r\n>good good study\r\n>day day up\r\n>good good study\r\n>day day up\r\n>good good study\r\n>day day up up\r\n```\r\n消费者\r\n```shell\r\n/opt/kafka_2.13-2.8.1/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic streams-wordcount-output --from-beginning --formatter kafka.tools.DefaultMessageFormatter --property print.key=true --property print.value=true --property key.deserializer=org.apache.kafka.common.serialization.StringDeserializer --property value.deserializer=org.apache.kafka.common.serialization.LongDeserializer\r\n# 输出\r\ngood\t6\r\nstudy\t3\r\nday\t6\r\nup\t3\r\ngood\t8\r\nstudy\t4\r\nday\t8\r\nup\t5\r\n```\r\n> 输出是一个连续的,不断被更新的流\r\n\r\n将应用重启后,producer生产新消息`good good study`,会发现下游topic中计数会更新\r\n```\r\ngood 10\r\nstudy\t5\r\n```\r\n\r\n\r\n```shell\r\n/opt/kafka_2.13-2.8.1/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list\r\n# 输出\r\n/opt/kafka_2.13-2.8.1/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list\r\n__consumer_offsets\r\nstreams-wordcount-output\r\ntext-lines-topic\r\nwordcount-app-KSTREAM-AGGREGATE-STATE-STORE-0000000004-changelog\r\nwordcount-app-KSTREAM-AGGREGATE-STATE-STORE-0000000004-repartition\r\nwordcount-app-counts-store-changelog\r\nwordcount-app-counts-store-repartition\r\n```\r\n\r\n"},{"id":"kafka架构","title":"Kafka架构","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"kafka架构","description":"1. topic kafka将消息以topic为单位进行分类,一个topic就是一个逻辑队列。类比于数据库中的分库。 2. partition 为了实现扩展性,提高并发能力,kafka将一个逻辑队列(topic)划分为多个partition,每个partition保存一个topic中的部分数据,每一...","relativePath":"Tech/Middleware/MessageQueue/kafka架构.md","rawContent":"![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410131749251.png)\r\n\r\n# 1. topic\r\nkafka将消息以topic为单位进行分类,一个topic就是一个逻辑队列。类比于数据库中的分库。\r\n# 2. partition\r\n为了实现扩展性,提高并发能力,kafka将一个逻辑队列(topic)划分为多个partition,每个partition保存一个topic中的部分数据,每一个partition中的顺序是有序的。类比于数据库中的分表。\r\n# 3. Replica\r\n副本,为实现备份的功能,保证集群中的某个节点发生故障时,该节点上的 Partition 数据不丢失,且 Kafka 仍然能够继续工作,Kafka 提供了副本机制,一个 Topic 的每个partition都有若干个副本,一个 Leader和若干个 Follower。\r\n# 4. Leader\r\n每个分区多个副本的“主”副本,生产者发送数据的对象,以及消费者消费数据的对象,都是 Leader。\r\n\r\n思考:\r\n> kafka不支持数据库类似的读写分离能力。kafka的每个broker节点其实即某个partion的leader,又是其他partition的follower。Kafka 的设计初衷是为了实现高吞吐量的消息传递,而不是传统数据库那样强调读写分离以满足复杂的查询和写入场景。它通过顺序读写磁盘、分区机制等设计来实现高效的数据写入和读取。\r\n# 5. Follower\r\n每个分区多个副本的“从”副本,实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发生故障时,某个 Follower 还会成为新的 Leader。\r\n# 6. Offset\r\n消费者消费的位置信息,监控数据消费到什么位置,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。\r\n# 7. Zookeeper\r\nKafka 集群能够正常工作,需要依赖于Zookeeper,Zookeeper 帮助 Kafka 存储和管理集群信息。\r\n# 8. broker\r\nkafka集群中包含一个或者多个服务实例(节点),这种服务实例被称为broker,用来存储消息数据。\r\n# 9. producer\r\n消息的生产者,负责发布消息到kafka的broker中。\r\n# 10. consumer\r\n消息的消费者,负责从kafka的broker中获取消息并处理。\r\n# 11. consumer group\r\n消费者组,每一个consumer都属于一个特定的consumer group,某个topic下的某个partition只会同时被consumer group中的一个consumer消费。\r\n\r\n[burningmyself.github.io/docs/micro/kafka.md at master · burningmyself/burningmyself.github.io · GitHub](https://github.com/burningmyself/burningmyself.github.io/blob/master/docs/micro/kafka.md)\r\n"},{"id":"kafka核心配置项","title":"Kafka核心配置项","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"kafka核心配置项","description":"这里我们以网络服务用的最多的kafka组件为例,来介绍下其实现原理,已经如何在知晓原理的情况下用好kafka。 1. producer - : 控制producer端开启幂等和事务能力。开启幂等后能够保证消息不重复。 - 为生产者提供了一个唯一的事务标识符。通过这个标识符,Kafka 能够跟踪和管理...","relativePath":"Tech/Middleware/MessageQueue/kafka核心配置项.md","rawContent":"\r\n这里我们以网络服务用的最多的kafka组件为例,来介绍下其实现原理,已经如何在知晓原理的情况下用好kafka。\r\n# 1. producer\r\n- `enable.idompotence`: 控制producer端开启幂等和事务能力。开启幂等后能够保证消息不重复。\r\n- `transactional.id`为生产者提供了一个唯一的事务标识符。通过这个标识符,Kafka 能够跟踪和管理生产者的事务状态。当生产者使用事务性发送时,Kafka 会将具有相同`transactional.id`的所有消息视为一个事务单元。这确保了在事务范围内的消息要么全部成功提交,要么全部回滚,从而保证了事务的原子性。\r\n- `acks`:此配置是 Producer 在确认一个请求发送完成之前需要收到的反馈信息的数量。 这个参数是为了保证发送请求的可靠性\r\n- `max.request.size`参数来控制消息的最大大小。这个参数指定了生产者在单个请求中发送的消息的总大小上限(包括消息体、键、头部等)。默认值为1048576B,即1MB\r\n- `retries`:配置生产者重试次数,对于可重试异常,那么只要在规定的次数内自行恢复了,就不会抛出异常,默认是0,不重试。\r\n- `retry.backoff.ms`用来设定两次重试之间的时间间隔,默认值100。\r\n- `request.timeout.ms`:配置Producer等待请求响应的最长时间,默认值为30000(ms),请求超时之后可以进行重试。注意这个参数需要比broker端参数`replica.lag.time.max.ms`的值要大,这样可以减少因客户端重试而引起的消息重复的概率。\r\n# 2. consumer\r\n- `max-poll-record`: 消费者每次从Kafka拉取的消息数量\r\n- `max.poll.records`:消费者一次最多拉取的数据条数\r\n- `max.poll.interval.ms`: 消费者每次从Kafka拉取数据的时间间隔,超时会认为消费者故障,触发rebalance,默认值为300000\r\n- `session.timeout.ms`:会话超时间,如果消费者在这个时间内没有发送心跳消息,kafka会认为这个消费者故障,触发rebalance,默认值为10000\r\n- `heartbeat-interval`:发送心跳的间时间,一般设置为`session.timeout.ms`的三分之一,默认为3000\r\n- `request.timeout.ms`: 客户端等待响应的最长时间,默认为30000\r\n- `fetch.min.bytes`:消费者从 broker 拉取消息时,每次请求最少返回的字节数。如果 broker 中可用的数据量小于这个值,broker 会等待直到有足够的数据再返回响应。\r\n- `fetch.max.bytes`:单次拉取数据的最大大小,应该要大于producer的max.request.size\r\n- `isolation.level`:控制消费者读取事务性消息的隔离级别。默认值是read_uncommitted\r\n\t1. `read_uncommitted`(可以读取未提交的事务消息)。\r\n\t2. `read_committed`(只能读取已提交的事务消息)。\r\n## 2.1. 消费任务超时\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406062306175.png)\r\n实际配置参数的时候建议要考虑应用的处理能力,当应用处理能力不足时,可以适当减少`max-poll-record`或提高`max.poll.interval.ms`时长。\r\n## 2.2. 心跳任务超时\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406062309777.png)\r\n考虑到某些业务的复杂度,消费任务时间处理比较久,`max.poll.interval.ms`配置值可能比较大,在client故障的场景下,可能需要等待较长的时间才能完成故障切换。为了保证能提供client故障判定的速度,kafka引入独立的心跳机制实现快速故障切换。\r\n## 2.3. 总结\r\nkafka的poll超时机制和心跳超时机制共同作用,保证了在允许消费者可以花较长的时间处理消息的同时,可以在较短的时间实现故障检测和故障切换。\r\n## 2.4. 提交offset\r\n- `enable.auto.commit`:是否自动提交消费偏移量。设置为`true`时,消费者会定期自动提交偏移量;设置为`false`时,需要手动提交偏移量。\r\n- `auto.commit.interval.ms`:当自动提交偏移量时,指定提交的时间间隔(毫秒)。\r\n- `auto-offset-reset`:\r\n\t- `earliest`:当各分区下有已提交的 offset 时,从提交的 offset 开始消费;无提交的 offset 时,从头开始消费。\r\n\t- `latest`:当各分区下有已提交的 offset 时,从提交的 offset 开始消费;无提交的 offset 时,消费新产生的该分区下的数据。\r\n\t- `none`:topic 各分区都存在已提交的 offset 时,从 offset 后开始消费;只要有一个分区不存在已提交的 offset,则抛出异常。\r\n当设置自动提交时,consumer会在每次拉取新数据前判断时间间隔是否到了来决定是否自动提交偏移量。\r\n> 自动提交会存在数据丢失和重复消费的风险"},{"id":"kafka生产消息","title":"Kafka生产消息","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":5,"slug":"kafka生产消息","description":"1. kafka获取partition对应节点 通过元数据获取 - Kafka producer 在启动时会向 Kafka 集群中的任意一个 broker(通常是配置中的 bootstrap.servers 列表中的一个)发送元数据请求(Metadata Request)。这个请求用于获取集群的元数...","relativePath":"Tech/Middleware/MessageQueue/kafka生产消息.md","rawContent":"# 1. kafka获取partition对应节点\r\n**通过元数据获取**\r\n- Kafka producer 在启动时会向 Kafka 集群中的任意一个 broker(通常是配置中的 bootstrap.servers 列表中的一个)发送元数据请求(Metadata Request)。这个请求用于获取集群的元数据信息,包括主题(topic)的分区(partition)分布情况以及每个 broker 的相关信息。\r\n- 例如,当producer第一次尝试发送消息到一个 topic 时,它会向集群询问这个 topic 的元数据。集群会返回这个 topic 有多少个分区,每个分区的副本分布在哪些 broker 上等信息。这些元数据会在 producer 本地缓存起来,并且会定期(可配置更新间隔)进行更新,以确保信息的准确性。\r\n**缓存更新机制**\r\n- 为了应对集群拓扑结构的变化(如新增 broker、broker 故障等),producer 会定期更新缓存的元数据。当有元数据更新时,例如某个分区的 leader 发生了变更,producer 会收到集群的通知或者在下次更新元数据请求时获取到新的信息。\r\n- 比如,当一个 broker 发生故障时,Kafka 集群会进行重新选举,选出新的分区 leader。producer 下一次更新元数据时,就会获取到这个新的 leader 信息,从而保证消息能够正确地发送到新的 leader 所在的 broker。\r\n# 2. 元数据\r\n在 Kafka 中,元数据主要存储在 Zookeeper 和 Kafka 控制器节点中。\r\n**一、存储在 Zookeeper 中的元数据**\r\nZookeeper 中存储了以下与 Kafka 相关的元数据信息:\r\n1. 主题(topic)列表:记录了当前 Kafka 集群中的所有主题名称。\r\n2. 分区(partition)信息:包括每个主题的分区数量、分区的领导者副本(leader replica)、跟随者副本(follower replica)等信息。\r\n3. 消费者群组(consumer group)信息:记录了消费者群组的 ID、消费者的成员信息以及消费的偏移量(offset)等。\r\n**二、存储在 Kafka 控制器节点中的元数据**\r\nKafka 控制器是一个特殊的 broker 节点,负责管理整个Kafka集群的状态。控制器节点中存储了以下元数据:\r\n1. 主题和分区的详细信息:包括分区的状态、副本的分布情况、副本的同步状态等。\r\n2. 领导者副本的选举信息:当领导者副本出现故障时,控制器负责选举新的领导者副本。\r\n3. 集群的配置信息:如 broker的列表、主题的配置参数等。\r\n\r\n# 3. 消息发送\r\n我们需要将 Producer 发送的数据封装成一个 ProducerRecord 对象,该对象需要指定一些参数:\r\n- topic:string 类型,NotNull。\r\n- partition:int 类型,可选。\r\n- timestamp:long 类型,可选。\r\n- key:string 类型,可选。\r\n- value:string 类型,可选。\r\n- headers:array 类型,Nullable。\r\n\r\n①指明 Partition 的情况下,直接将给定的 Value 作为 Partition 的值。\r\n②没有指明 Partition 但有 Key 的情况下,将 Key 的 Hash 值与分区数取余得到 Partition 值。\r\n③既没有 Partition 有没有 Key 的情况下,第一次调用时随机生成一个整数(后面每次调用都在这个整数上自增),将这个值与可用的分区数取余,得到 Partition 值,也就是常说的 Round-Robin 轮询算法。"},{"id":"kafka高可靠","title":"Kafka高可靠","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":17,"slug":"kafka高可靠","description":"1. 生产可靠 为保证Producer发送的数据,能可靠地发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送ACK(ACKnowledge 确认收到)。如果Producer收到ACK,就会进行下一轮的发送,否则重新发送数据。 1....","relativePath":"Tech/Middleware/MessageQueue/kafka高可靠.md","rawContent":"# 1. 生产可靠\r\n为保证Producer发送的数据,能可靠地发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送ACK(ACKnowledge 确认收到)。如果Producer收到ACK,就会进行下一轮的发送,否则重新发送数据。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410131811443.png)\r\n## 1.1. 副本同步策略\r\n何时发送ACK?确保有Follower与Leader同步完成,Leader再发送ACK,这样才能保证Leader挂掉之后,能在Follower中选举出新的Leader而不丢数据。多少个Follower同步完成后发送 ACK?全部Follower同步完成,再发送ACK。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410131812226.png)\r\n采用第二种方案,所有Follower完成同步,Producer才能继续发送数据,设想有一个Follower因为某种原因出现故障,那Leader就要一直等到它完成同步。这个问题怎么解决?Leader维护了一个动态的in-sync replica set(ISR):和Leader保持同步的Follower集合。当ISR集合中的Follower完成数据的同步之后,Leader 就会给 Follower 发送 ACK。如果 Follower 长时间未向 Leader 同步数据,则该 Follower 将被踢出 ISR 集合,该时间阈值由`replica.lag.time.max.ms`参数设定。Leader发生故障后,就会从ISR中选举出新的 Leader。\r\n\r\n对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR中的Follower全部接受成功。所以 Kafka 为用户提供了三种可靠性级别,用户根据可靠性和延迟的要求进行权衡,选择以下的配置。\r\n- 0:Producer不等待Broker的ACK,这提供了最低延迟,Broker一收到数据还没有写入磁盘就已经返回,当Broker故障时有可能丢失数据。\r\n- 1:Producer等待Broker的ACK,Partition的Leader落盘成功后返回 ACK,如果在Follower同步成功之前 Leader 故障,那么将会丢失数据。\r\n- -1(all):Producer 等待 Broker 的 ACK,Partition 的 Leader 和 Follower 全部落盘成功后才返回 ACK。但是在 Broker 发送 ACK 时,Leader 发生故障,则会造成数据重复。\r\n> -1的时候能够保证at least once,但不能保证exactly once\r\n## 1.2. 故障处理细节\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410132028418.png)\r\n- LEO:每个副本最大的Offset。\r\n- HW:消费者能见到的最大的Offset,ISR队列中最小的 LEO。\r\n\r\n**Follower 故障**:Follower发生故障后会被临时踢出ISR集合,待该Follower恢复后,Follower会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从HW开始向 Leader 进行同步数据操作。等该 Follower的LEO大于等于该Partition的HW,即Follower追上Leader后,就可以重新加入ISR了。\r\n\r\n**Leader 故障**:Leader发生故障后,会从ISR中选出一个新的 Leader,之后,为保证多个副本之间的数据一致性,其余的 Follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 Leader 同步数据。\r\n> HW和数据的同步是独立的,HW只能保证数据的一致性,无法保证不丢数据。\r\n# 2. 消费可靠\r\n由于Consumer在消费过程中可能会出现断电宕机等故障,Consumer恢复后,需要从故障前的位置继续消费。所以Consumer需要实时记录自己消费到了哪个Offset,以便故障恢复后继续消费。\r\nKafka 0.9 版本之前,Consumer默认将Offset保存在 Zookeeper 中,从 0.9 版本开始,Consumer 默认将Offset 保存在Kafka 一个内置的Topic中,该Topic为 `__consumer_offsets`。\r\n\r\nKafka 使用特定的哈希算法来确定消费者组的偏移量存储在`__consumer_offsets`主题的哪个分区中。通常,这个算法会考虑消费者组 ID、主题名称和分区编号等因素进行哈希计算,然后根据计算结果确定存储分区。`__consumer_offsets`主题的每个分区中存储了多个键值对,其中键通常由消费者组 ID、主题名称和分区编号组成,值则是具体的偏移量数值以及其他相关的元数据信息。\r\n# 3. controller和zookeeper\r\nhttps://www.51cto.com/article/754629.html\r\n## 3.1. Zookeeper\r\n### 3.1.1. 存储元数据\r\n- Zookeeper 主要负责存储和管理 Kafka 集群的元数据信息,如broker列表、主题列表、分区分配等。在副本的leader选举过程中,controller需要从Zookeeper中获取这些元数据信息,以便确定哪些分区需要进行领导者选举。\r\n### 3.1.2. Controller和coodinator的选举(如果当前 controller 宕机)\r\n- 在 Kafka 集群中,当前 controller 宕机时,其他brokers会竞争成为新的controller。\r\n- 这个过程依赖于Zookeeper。Brokers会在Zookeeper中注册一个临时节点,用于竞选 controller。Brokers 通过在Zookeeper中创建临时节点来竞争controller角色,Zookeeper会保证只有一个节点能够成功创建,第一个成功创建临时节点的broker成为新的controller。\r\n\r\n详细过程如下:\r\n1. 注册Controller节点当Kafka集群启动时,每个Broker都会尝试在Zookeeper中的/controller路径下创建一个临时节点。因为同一时刻只能存在一个/controller 节点,所以只有一个 Broker 成功创建节点并成为Controller。其他 Broker 会收到节点创建失败的通知,然后转为观察者(Observer)状态,监视Controller节点路径的变化。\r\n2. 监听 Controller 节点所有非Controller的 Broker 都会在 Zookeeper 中对 /controller 路径设置一个 Watcher 事件。这样当Controller节点发生变化时(例如,Controller失效),所有非Controller就会收到一个 Watcher 事件。\r\n3. 选举新的Controller当某个Broker接收到Controller节点变化的通知后,它会再次尝试在 Zookeeper 中的/controller 路径下创建一个临时节点。与启动时的过程类似,只有一个 Broker 能够成功创建节点并成为新的Controller。新Controller会在选举成功后接管集群元数据的管理工作。\r\n4. 更新集群元数据新Controller在选举成功后需要更新集群元数据,包括分区状态、副本状态等。同时,新控制器会通知所有相关的 Broker 更新它们的元数据信息。这样,集群中的所有 Broker 都能够知道新Controller的身份,并进行协同工作。\r\n> 注意:临时节点的特点是在创建它的客户端(即 Broker节点)断开连接时,它会自动被 Zookeeper 删除。这种机制保证了只有一个Broker节点能够成为控制器,以避免多个控制器同时对集群元数据进行操作引发的问题。\r\n## 3.2. Controller\r\n### 3.2.1. 存储数据\r\n- 分区的leader和ISR信息\r\n- 跟踪正在进行的leader选举和重分配的分区列表\r\n- 副本状态变化等动态信息\r\n### 3.2.2. 作用\r\nController是Kafka集群中的一个特殊broker,它负责管理整个集群的状态。它承担着主题创建、分区分配、副本选举等关键任务,直接参与到 Kafka 的核心业务流程中。Controller能够快速响应集群中的各种变化,并及时做出相应的调整,以保证集群的高可用性和稳定性。\r\n1. Broker状态管理:Controller会跟踪集群中所有Broker的在线状态,并在Broker宕机或者恢复时更新集群的状态。\r\n2. 分区状态管理:当新的Topic被创建,或者已有的Topic被删除时,Controller会负责管理这些变化,并更新集群的状态。\r\n3. 分区领导者选举:当一台Broker节点宕机时,并且宕机的机器上包含分区领导者副本时,Controller会负责对其上的所有Partition进行新的领导者选举。\r\n4. 副本状态管理:Controller负责管理Partition的ISR列表,当Follower副本无法及时跟随Leader副本时,Controller会将其从ISR列表中移除。\r\n5. 分区重平衡:当添加或删除Broker节点时,Controller会负责对Partition的分布进行重平衡,以确保数据的均匀分布。\r\n6. 存储集群元数据:Controller保存了集群中最全的元数据信息,并通过发送请求同步到其他Broker上面。\r\n# 4. broker\r\n1. 数据存储:每个非Controller节点都存储一部分数据,这部分数据是由Topic的Partition组成的。这意味着,每个Broker都保存了特定Partition的所有数据,不论这个Partition是Leader还是Follower。\r\n2. 数据复制:为了保证数据的可靠性,Kafka系统通过数据复制机制在多个Broker之间备份数据。每个Topic的Partition都有一个Leader和多个Follower。Leader负责处理所有的客户端读写请求,而Follower负责从Leader复制数据。在这个过程中,非Controller节点既可以是Leader也可以是Follower。\r\n3. 处理客户端请求:非Controller节点(leader节点)负责处理来自Producer和Consumer的请求。对于Producer的写请求,Broker会将数据写入对应的Partition。对于Consumer的读请求,Broker会从对应的Partition读取数据。\r\n4. 参与Leader选举:当Partition的Leader节点出现故障时,非Controller节点可能被选举为新的Leader节点。虽然Leader选举过程由Controller节点协调,但所有的非Controller节点都需要参与这个过程。\r\n5. 故障恢复:当某个Broker宕机时,Kafka会自动重新分配其上的Partition的Leader角色给其他的Broker,这也是非Controller节点的重要职责之一。\r\n# 5. coordinator\r\n## 5.1. 分类\r\n### 5.1.1. consumer group coordinator\r\n负责协调和管理消费者组,本质上是由broker充当的,每个consumer group都会有一个对应的broker充当其coordinator,主要负责以下功能:\r\n1. coordinator负责接收这个consumer group里面的所有consumer的心跳,如果发现某个consumer失联了,coordinator就会触发rebalance。\r\n2. 维护消费者组的成员信息,包括消费者ID,消费进度,还会处理消费者的joinGroup和leave Group请求并触发rebalance。\r\n3. 根据消费者组中消费者的数量以及topic对应的分区数量,制定消息分配方案,哪个消费者应该消费哪个分区,以确保负载均衡和高效消费。Kafka中的一个partition只会被一个consumer消费。\r\n### 5.1.2. transaction coordinator\r\n负责事务协调,每个事务型producer都要和transaction coordinator交互\r\n1. 确保事务的原子性,即所有消息要不全部写入成功,要不全部不写入\r\n2. 处理事务的开始、提交、回滚等操作,确保消息的一致性和可靠性\r\n#### 5.1.2.1. 确保事务性消息的原子性操作的基本原理\r\n1. **事务的开始:** 生产者启动事务时,它向事务 Coordinator 发送一个事务的起始请求。在这个阶段,事务 Coordinator 会为该事务分配一个唯一的事务 ID。 \r\n2. **事务性生产:** 在事务启动后,生产者可以发送一批消息,并在发送的消息中关联这个事务 ID。这确保了这批消息属于同一个事务。 \r\n3. **事务的提交:** 如果在事务期间一切正常,生产者将向事务 Coordinator 发送提交请求。在接收到提交请求后,事务 Coordinator 将事务的状态设置为提交,并将相关消息持久化到 Kafka 服务器。 \r\n4. **事务的回滚:** 如果在事务期间发生错误或者生产者决定回滚事务,生产者将向事务 Coordinator 发送回滚请求。事务 Coordinator 将事务的状态设置为回滚,并丢弃相关消息。 \r\n5. **消费者的参与:** 消费者也可以参与到事务中,以确保在消费消息时能够参与到相同的事务中,保持消息的一致性。 \r\n6. **幂等性:** Kafka 还支持幂等性生产者,这意味着即使生产者在发送消息时失败,重试时也不会引入重复的消息。这与事务性生产者结合使用,可以提供更强的消息传递保障。 \r\n## 5.2. 选举与切换\r\n### 5.2.1. Coordinator 的选举过程:\r\ncoordinator选举机制确保了当前活跃的coordinator出现故障时,集群能够选举出一个新的coordinator来接管协调工作,从而保证整个系统的高可用性。kafka 2.8版本以下支持通过zookeeper进行选举,2.8版本以上新增了基于Kraft算法的选举机制。\r\n基于zookeeper的Kafka Coordinator选举过程可以概括为以下几个步骤:\r\n1. **Coordinator的注册**\r\n\t每个Broker启动时,都会先在Zookeeper中注册自己有资格竞选哪些Coordinator。不同的Coordinator有不同的注册路径,如/controller,/admin等。\r\n2. **选举触发**\r\n\t当没有活跃的Coordinator时,集群中剩余的Broker会开始发起新一轮的选举程序。常见的触发情况包括: 活跃Coordinator宕机、大多数Broker重启导致原Coordinator失去集群大多数票数等。每个broker会定期向zookeeper发送心跳来证明自己活着,如果长时间没有心跳,zookeeper就认为该broker已经故障。\r\n3. **选举过程**\r\n\t所有参与选举的Broker会先在Zookeeper上创建临时节点,节点路径格式为`/${COORDINATOR_TYPE}/candidate/control_path_${CONTROL_PATH_VERSION}_${BROKER_ID}-${GENERATED_ID}`。 然后各Broker按照节点路径的字典序排列,字典序最小的临时节点所对应的Broker就是被选举为新的Coordinator。\r\n4. **胜出通知**\r\n\t被选为新Coordinator的Broker会收到来自Zookeeper的通知,它会创建`${COORDINATOR_TYPE}/controller_epoch`持久节点,并写入自己的BrokerID和纪元值(epoch),其它Broker发现这个节点后就知道新Coordinator是谁了。\r\n6. **Coordinator上线**\r\n\t新选举产生的Coordinator会先从Zookeeper获取所需的元数据信息,如分区副本分配方案等。之后它会开始发送请求给各个Broker,接管整个集群的协调工作。\r\nhttps://developer.aliyun.com/article/1479596\r\n"},{"id":"kafka高性能读写","title":"Kafka高性能读写","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":7,"slug":"kafka高性能读写","description":"核心: - 顺序读写 - page cache,避免每次读取磁盘。也正是因为顺序读写,避免了大量page cache miss,可以充分利用page cache。 - 零拷贝。 1. 存储结构 一个topic下多个partition,每个partition使用一个文件夹存储。partition命名规...","relativePath":"Tech/Middleware/MessageQueue/kafka高性能读写.md","rawContent":"核心:\r\n- 顺序读写\r\n- page cache,避免每次读取磁盘。也正是因为顺序读写,避免了大量page cache miss,可以充分利用page cache。\r\n- 零拷贝。\r\n\r\n# 1. 存储结构\r\n一个topic下多个partition,每个partition使用一个文件夹存储。partition命名规则为:topic名称-序号(从0开始)\r\n一个partion分为多个大小大致相等的segment,kafka的数据清理是以segment为最小单位的,当数据超过最长保存时间或最大保存空间后,会优先释放最早的segment。`log.segment.bytes`,此配置是指 log 日志划分成块的大小,默认值 1G。\r\n\r\nsegment命名规则:partion全局的第一个segment从0开始,后续每个segment文件命名为上一个segment文件最后一条消息的offset值+1。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410131617661.png)\r\n\r\n一个segment分为如下三个文件:\r\n- xxx.log文件\r\n- xxx.index文件\r\n- xxx.timeindex文件\r\n# 2. 稀疏索引 \r\nsegment index file采取稀疏索引存储方式,不会为每条数据创建索引,大大的减少索了引文件大小。可以通过配置项`log.index.interval.bytes`控制。稀疏索引间存储数据的大小,默认4kb,kafka每当写入了4kb大小的日志(.log),然后就往index文件里面记录一个索引。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410131619339.png)\r\n\r\n> 稀疏索引占用空间小,查询相对稠密索引慢;稠密索引占用空间大,查询快\r\n# 3. 查找过程\r\n根据offset二分查找文件列表,就可以快速定位到具体的.log和.index文件。 如当offset=600时定位到00000000000000000522.index|log文件。在index文件中根据offset来查找到最近的不大于当前查找目标的记录,找到对应的position。索引文件中的position是相对于日志文件(.log 文件)的首地址。position记录了特定offset对应的消息在日志文件中的具体位置,这个位置是从日志文件开头开始计算的偏移量。通过这个相对位置,可以快速在日志文件中定位到对应的消息进行读取操作。通过position快速跳转到log对应位置然后顺序查找到所用的消息。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410131621711.png)\r\n\r\n> kafka拉数据不是每次都从磁盘上获取的,顺序读的特性可以很好地利用page cache机制,直接从内存中获取数据。\r\n\r\n> timeindex文件存储的了时间戳和offset的映射关系,主要是用于基与时间戳快速定位消息。采用的也是稀疏索引。\r\n\r\nkafka的性能与内存大小有关,内存越大,page cache存储的数据就越多,读写越快。\r\n# 4. # leader-epoch-checkpoint\r\n在Apache Kafka中,`leader-epoch-checkpoint` 主要涉及到Leader Epoch机制,这是一种用于管理Kafka集群中分区领导者变更的关键机制,目的是为了简化和增强领导者选举和数据一致性方面的功能。\r\n\r\n```\r\n0 #版本号\r\n1 #下面的记录行数 \r\n29 2485991681 #leader epoch ,可以看到有两位值(epoch,offset)。 \r\n\r\n## epoch表示leader的版本号,从0开始,当leader变更过1次时epoch就会+1\r\n## offset则对应于该epoch版本的leader写入第一条消息的位移\r\n```\r\n\r\n# 5. 零拷贝\r\n在传统的数据读取和发送方式中,数据从磁盘文件读取到内核缓冲区,再从内核缓冲区复制到用户缓冲区,然后从用户缓冲区再复制到内核的套接字缓冲区(用于网络发送),最后发送到网络。这种多次数据复制会带来大量的 CPU 开销和内存带宽占用,降低了系统性能。\r\n\r\n零拷贝(Zero-Copy)是一种`I/O` 操作优化技术,可以快速高效地将数据从文件系统移动到网络接口,而不需要将其从内核空间复制到用户空间。其在 `FTP` 或者 `HTTP` 等协议中可以显著地提升性能。但是需要注意的是,并不是所有的操作系统都支持这一特性,目前只有在使用 `NIO` 和 `Epoll` 传输时才可使用该特性。需要注意,它不能用于实现了数据加密或者压缩的文件系统上。零拷贝大大提高了应用程序的性能,减少了内核和用户模式之间的上下文切换 。对 Linux操作系统而言,零拷贝技术依赖于底层的sendfile() 方法实现 。对应于Java 语言,Fi leChannal.transferTo()方法的底层实现就是 sendfile()方法 。\r\n> 零拷贝技术通过 DMA (Direct Memory Access) 技术将文件内容复制到内核模式下的 Read Buffer 中。零拷贝是针对内核模式而言的, 数据在内核模式下实现了零拷贝。\r\n\r\nKafka 利用了`sendfile()`系统调用。当 Kafka 的生产者发送消息或者消费者读取消息时,数据可以直接从文件系统缓存(内核缓冲区)通过`sendfile()`系统调用发送到网络接口(套接字缓冲区),避免了数据从内核缓冲区到用户缓冲区再回到内核缓冲区(套接字缓冲区)的多余复制。例如,假设 Kafka 存储的日志文件(存储消息数据)已经在操作系统的页面缓存(内核缓冲区)中,当需要将数据发送给消费者时,`sendfile()`直接将页面缓存中的数据传输到网络套接字,减少了数据复制的次数。\r\n\r\n# 6. 思考\r\nkafka中topic的数量是否有上限;partition的数量是否有上限\r\n- 增加broker节点数量\r\n- 增加broker磁盘数量\r\n- 增加内存大小\r\n\r\n# 7. 参考文献\r\n[一文彻底弄懂零拷贝原理零拷贝(Zero-Copy)是一种 I/O 操作优化技术,可以快速高效地将数据从文件系统移动到网络 - 掘金 (juejin.cn)](https://juejin.cn/post/6995519558475841550)"},{"id":"不同消息队列对比","title":"不同消息队列对比","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"不同消息队列对比","description":"- RocketMQ 的存储结构相对较为灵活,可以针对不同的场景进行优化。它采用了单一的 commitlog 文件来顺序存储所有消息,然后通过索引文件快速定位特定topic的消息。这种设计使得在处理大量 topic 时,存储资源的分配更加高效。每个topic都对应独立索引文件,加上page cach...","relativePath":"Tech/Middleware/MessageQueue/不同消息队列对比.md","rawContent":"![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410131751148.png)\r\n\r\n\r\n- RocketMQ 的存储结构相对较为灵活,可以针对不同的场景进行优化。它采用了单一的 commitlog 文件来顺序存储所有消息,然后通过索引文件快速定位特定topic的消息。这种设计使得在处理大量 topic 时,存储资源的分配更加高效。每个topic都对应独立索引文件,加上page cache机制,所以读的性能也不差。\r\n\r\nkafka的问题\r\n- Topic 及 Topic 分区总量不断增加,集群性能受到影响:Kafka 高性能依赖于磁盘的顺序读写,磁盘上大量分区导致随机读写加重;\r\n- 业务流量增加迅速,存量集群变大,需要将老的业务进行资源组隔离迁移或者集群拆分。无论是资源组隔离还是集群的隔离的方式,由于集群不可以进行动态扩缩容,机器不能够进行灵活调配,都存在利用率不高、运维成本增加的问题;\r\n- 机器扩容慢,需要做长时间流量均衡,难以应对突发流量。集群规模越大,问题越突出;\r\n- 消费端性能扩展太依赖分区扩容,导致集群元数据疯狂增长;\r\n- 集群数量对应的机器基数大,硬件故障概率高,出现硬件故障时影响会直接传导到客户端,缺少中间层容错。\r\n\r\nPulsar 采用计算存储层分离架构。计算层的 Broker 节点是对等且无状态的,可以快速扩展;存储层使用BookKeeper作为节点,同样节点对等。这种分离架构支持计算和存储层各自独立扩展。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410132224101.png)\r\n其次,Pulsar 的各个节点都是轻量化的,在出现故障和宕机时可以快速恢复。一般情况下可以通过快速上下线来解决某个节点机器的问题。同时 Broker 层可以作为 BookKeeper 层的容错层,可以防止故障直接传导至用户端。\r\n\r\n[构建下一代万亿级云原生消息架构:Apache Pulsar 在 vivo 的探索与实践_大数据_陈建波_InfoQ精选文章](https://www.infoq.cn/article/zynhqsxpd0n7vwhcubtt)"},{"id":"云时代的消息中间件","title":"云时代的消息中间件","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"云时代的消息中间件","description":"","relativePath":"Tech/Middleware/MessageQueue/云时代的消息中间件.md","rawContent":""},{"id":"消息队列核心","title":"消息队列核心","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":13,"slug":"消息队列核心","description":"- 为什么需要消息队列 - 消息队列的核心设计和使用 - 深入消息队列高性能原理 cap原则 1. 为什么需用消息队列 1.1. 业务解耦 业务解耦,代码解耦。现在的云网络服务大都遵循SDN设计思想,将服务拆分成管控转三部分,不同组件负责承载不同的业务,其解耦方式之一就是通过消息队列。很多消息队列都...","relativePath":"Tech/Middleware/MessageQueue/消息队列核心.md","rawContent":"- 为什么需要消息队列\r\n- 消息队列的核心设计和使用\r\n- 深入消息队列高性能原理\r\n\r\ncap原则\r\n\r\n# 1. 为什么需用消息队列\r\n## 1.1. 业务解耦\r\n业务解耦,代码解耦。现在的云网络服务大都遵循SDN设计思想,将服务拆分成管控转三部分,不同组件负责承载不同的业务,其解耦方式之一就是通过消息队列。很多消息队列都叫broker,即代理,其实就很好的表现了消息队列的功能。\r\n## 1.2. 广播\r\n消息队列的基本功能之一是进行广播。如果没有消息队列,每当一个新的业务方出现,我们都要联调一次新接口。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,是下游的事情,无疑极大地减少了开发和联调的工作量。如VPC作为网络的底座,其port的创删改消息被很多其他网络服务关注,如果不通过消息队列进行广播,那么VPC的port相关业务将会变得非常臃肿。\r\n## 1.3. 同步变异步 \r\n有些RPC场景不需要进行同步调用,可以通过同步变异步方式加快响应速度。比如网络服务,管理面资源创删改到数据面生效往往需要很长时间,如果不通过消息队列进行解耦,那么用户的API体验将会非常糟糕,接口超时会变成常态。\r\n## 1.4. 流量削峰\r\n消息中间件提供的核心之一为消息队列,可堆积大量的消息。在我们的整个云网络架构中,不同的云服务组件具有不同的性能,当上游系统的吞吐能力远高于下游系统,在流量洪峰时可能会冲垮下游系统。而通过消息中间件可以在峰值时堆积消息,在峰值过去后下游系统慢慢消费。\r\n## 1.5. 可恢复性\r\n即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。\r\n# 2. 常见的消息队列形式\r\n- 内存存储,如quark底座采用的akka框架就使用了MailBox来实现API请求的排队。\r\n- 持久化存储,如kafka、rabbitMQ、rocketMQ。\r\n# 3. 消息队列的核心设计\r\n## 3.1. 消息保序\r\n消息的保序性可以分为两部分,生产有序和消费有序。消息对于消息队列来说是透明的,不会理解消息谁前谁后,所以生产的有序性一般需要业务来保证,一般可以通过对资源加锁来控制资源不会并发操作,因此下文我们主要讨论消费有序。\r\n### 3.1.1. 多队列模型\r\n如果队列有多个的情况下,无法保证全局有序。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406062340164.png)\r\n### 3.1.2. 单队列模型\r\n单队列可以保证生产和消费有序\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406062347796.png)\r\n但此时消费者也需要单线程处理。如果多个消费者或多线程同时处理队列中的不同消息也会导致消费乱序。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406070005678.png)\r\n\r\n### 3.1.3. 局部保序\r\n- 多队列可以提高生产和发送消息的效率,但无法保证全局有序。\r\n- 单队列可以保证全局有序,但是性能较低\r\n> kafka属于典型的多队列模型,但是提供了一种局部保序的能力。kafka提供了分区partition key和consumer balance的机制来保证了一个topic下的某个partition里的消息可以被有序消费。\r\n#### 3.1.3.1. 生产有序\r\n在发消息的时候指定`Partition Key`实现把某一类的消息都放入同一个Partition。例如云上project是资源隔离的最小单位,不同租户之间的资源处理的顺序并不会导致系统数据异常。我们可以使用`project_id`做key,这样同一个租户的消息肯定是在同一个partition中的,就保证了这个租户的的消息生产是有序的。\r\n#### 3.1.3.2. 消费有序\r\n同时kafka的balance机制,保证了一个topic下面的某个partition只会被一个consumer group中的某一个consumer消费,因此保证了消费有序。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202406070013896.png)\r\n## 3.2. 消息推送模型\r\n\r\n| | 弱消费者 | 消息时延 | 顺序消息 |\r\n| ---- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |\r\n| push | 如果consumer的能力比producer的弱很多,势必造成消息在broker的堆积。broker需要考虑consumer的消息能力,如果broker给consumer推送一堆消息,consumer无法处理,导致broker压力剧增。 | push模型可以在消息到来的那一刻立刻推送给consumer,实现相对较低的时延 | 单partition只支持一个消费者消费,并且消费者只有确认一个消息消费后才能push送另外一个消息,还要发送者保证全局顺序唯一,成本太高了。尤其是必须每个消息消费确认后才能发下一条消息,这对于本身堆积能力和慢消费就是瓶颈的push模式的消息队列,简直是一场灾难。 |\r\n| pull | consumer可以按需消费,不用担心消息过多处理不了。broker不用关心每个consumer的消费水平,设计相对简单,仅需维护好每个consumer的消费进度即可,甚至也可以完全让consumer自己维护。 | 这属于pull模式最大的短板,消费方无法准确地决定何时有消息达到,何时应该去拉消息。 | 实现简单
1. producer对应partition,并且单线程。
2. consumer对应partition,消费确认(或批量确认),继续消费即可 |\r\n\r\nkafka采用long polling模型,可以一定程度上保证消息的及时性,其及时性主要受消费者消费速度的影响,如果消费者消息处理的非常快,那么就可以立即发起下一次poll,可以达到伪同步的效果。\r\n## 3.3. 消息投递可靠性\r\n- at most once: 消息可以丢,但是绝不会重复\r\n- at least once: 消息不可以丢,但是可以重复\r\n- exactly once: 消息不回丢,也不会重复\r\n### 3.3.1. at most once\r\nproducer: 发送完之后,不管broker是否响应,都不会再进行重试(传输层协议UDP就是保证at most once)\r\nconsumer:pull或push消息后,不管consumer是否消费成功,offset都后移。\r\n### 3.3.2. at least once\r\nproducer: 需要在网络出现超时重新发送相同的消息,也就是引入超时重试的机制,保证发出的每条消息都收到了broker确定的响应结果。\r\nconsumer: 消费端消费的每一条数据都通过ack机制进行确认\r\n### 3.3.3. exactly once\r\n考虑网络并不是完全可靠的,分布式系统想要实现理论上的exactly once是不可能的。\r\n> kafka提供的是at least once模型,但是可以通过幂等性(Idempotence)来一定程度实现这种语义。\r\n#### 3.3.3.1. 生产端幂等\r\nkafka可以通过开启`enable.idompotence`配置项一键开启幂等能力,无需任何其他配置和额外代码编写。\r\n- kafka会为每个producer在每个一个topic的每一个partition上生成一个三元组`(PID, TOPIC, Partition)`\r\n- 以及对应的sequence号来唯一标识一条消息,和consumer端的offset类似,sequence号保证单调递增。\r\nbroker端会记录三元组和sequence号信息,broker会被收到的消息进行sequence号校验,如果检测到sequence号和已经记录的消息冲突,那么broker就会拒绝这次写入请求。\r\n- kafka只能保证**单会话**上的幂等性,当producer重启后,就会被分配一个新的PID。\r\n- kafka只能保证单分区的幂等性,因为produce在不同的partition上具有不同的三元组`(PID, TOPIC, Partition)`。\r\n#### 3.3.3.2. 消费端幂等\r\n- 将`enable.auto.commit`设置为false\r\n- 在处理消息的时候,记录当前处理成功的每个消息的offset\r\n- 当一批消息全部处理成功的时候通过`consumer.commitSync()`手动提交;当一批消息部分处理成功通过`seek(TopicPartition partition, long offset)`方法来手动移动消费进度到最后一条处理成功的消息。\r\n> 如果消费端支持消息幂等处理的情况下,也可以都过nack的方式回滚当前批次的全部消息,对当前批次消息进行全部重试。\r\n## 3.4. 事务能力\r\n类似常见的数据库事务能力,消息队列的一致性用于保证多条消息要么全部成功,要么全部失败,kafka提供的事务默认默认是`read committed`级别。\r\n[Kafka事务「原理剖析」 - 昔久 - 博客园 (cnblogs.com)](https://www.cnblogs.com/xijiu/p/16917741.html)\r\n\r\n两阶段提交原理 tcc原理(tcc)\r\n### 3.4.1. producer\r\n在producer端开启事务能力,需要满足以下两个条件\r\n- 配置`enable.auto.commit=true\r\n- 配置`transactional.id`,可配置为主机uuid或hostname\r\n在此基础上,服务端代码也需要按如下结构进行适配:\r\n```java\r\nproducer.initTransactions();\r\ntry {\r\n producer.beginTransaction();\r\n producer.send(record1);\r\n producer.send(record2);\r\n producer.commitTransaction();\r\n} catch (KafkaException e) {\r\n producer.abortTransaction();\r\n}\r\n```\r\n### 3.4.2. consumer\r\n开启事务后,producer在事务下部分消息写入失败,部分消息写入成功,Kafka实际上已经把这部分成功的数据写入到了底层的日志中,默认情况下consumer还是会看到这些消息。因此在consumer端,读取事务型producer发送的消息也需要进行一定程度的适配,通过设置`isolation.level`参数的值即可。当前这个参数有两个取值:\r\n- read_uncommitted:这是默认值,表明consumer能够读取到任何写入kafka的消息,不论事务型producer提交事务还是终止事务。\r\n- read_committed:表明consumer只会读取事务型producer成功提交事务写入的消息。\r\n### 3.4.3. 数据库与kafka消息的一致性\r\n#### 3.4.3.1. 方案一\r\n数据库事务中发送Kafka消息,性能差,且Kafka的故障会导致整体业务受损。最好做到异步。\r\n#### 3.4.3.2. 方案二\r\n事务消息适用于异步更新的场景,对数据的实时性要求不高的地方,主要目的是为了解决消息生产者和消息消费者的数据一致性问题。使用消息队列MQ的时候,我们可以采用write ahead和write done的方式来保证事务的一致性。开启事务之前,先发送write ahead消息,告知消息消费者事务开启,随后生产者执行事务,并发送write done消息,如果write done消息发送成功,则可能保证消息消费者能够正确拿到消息,如果write done消息发送失败,则需要消息消费者根据ahead消息维护一个定时器,在超时后通过生产者提供的接口进行资源的反差,确认生产者的事务是否执行成功,并执行相应的消费或放弃操作。这里需要保证ahead消息中包括能够确认资源唯一性的键,通常使用资源主键替代。\r\n\r\n![](https://raw.githubusercontent.com/shinerio/shinerio.github.io/blog-images/%E5%B0%8F%E4%B9%A6%E5%8C%A0/1638512362859.png)\r\n"},{"id":"Middleware","title":"Middleware","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"middleware","description":"","relativePath":"Tech/Middleware/Middleware.md","rawContent":""},{"id":"redis","title":"Redis","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"redis","description":"","relativePath":"Tech/Middleware/redis/redis.md","rawContent":""},{"id":"redis关键参数","title":"Redis关键参数","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"redis关键参数","description":"1. go-redis 在go- redis中,连接设置是通过 Options 结构体来管理的。 1.1. 单机模式 - Network - 网络类型,可以是 tcp 或 unix,默认值为 tcp。 - Addr - Redis 服务器的 host:port 地址。 - Dialer - 创建新网...","relativePath":"Tech/Middleware/redis/redis关键参数.md","rawContent":"# 1. go-redis\r\n在go- redis中,连接设置是通过 Options 结构体来管理的。\r\n\r\n## 1.1. 单机模式\r\n- Network - 网络类型,可以是 tcp 或 unix,默认值为 tcp。\r\n- Addr -  Redis 服务器的 host:port 地址。\r\n- Dialer - 创建新网络连接的函数,优先于Network和Addr选项。\r\n- OnConnect - 新连接建立时调用的钩子函数。\r\n- Username - 连接 Redis 6.0 及更高版本时使用的用户名,用于身份验证。\r\n- Password - 连接 Redis 时使用的可选密码。\r\n- DB - 连接后选择的数据库编号。\r\n- MaxRetries - 在放弃之前的最大重试次数,默认是 3。\r\n- MinRetryBackoff - 每次重试之间的最小等待时间,默认是 8 毫秒。\r\n- MaxRetryBackoff - 每次重试之间的最大等待时间,默认是 512 毫秒。\r\n- DialTimeout - 建立新连接的超时时间,默认是 5 秒。\r\n- ReadTimeout - 套接字读取的超时时间,默认是 3 秒。\r\n- WriteTimeout - 套接字写入的超时时间,默认是 3 秒。\r\n- PoolTimeout - 如果所有连接都忙碌,客户端等待连接的时间,默认是 ReadTimeout + 1 秒。 \r\n- MinIdleConns - 最少的idle状态的连接数\r\n- IdleTimeout:Amount of time after which client closes idle connections. // Should be less than server's timeout. // Default is 5 minutes. -1 disables idle timeout check.\r\n- MaxConnAge - Connection age at which client retires (closes) the connection. // Default is to not close aged connections.\r\n- IdleCheckFrequency:Frequency of idle checks made by idle connections reaper. // Default is 1 minute. -1 disables idle connections reaper, // but idle connections are still discarded by the client // if IdleTimeout is set.\r\n- Limiter - 用于实现断路器或速率限制的接口。 \r\n\r\n## 1.2. 哨兵模式\r\n```go\r\ntype FailoverOptions struct {\r\n\t// The master name.\r\n\tMasterName string\r\n\t// A seed list of host:port addresses of sentinel nodes.\r\n\tSentinelAddrs []string\r\n\t// Sentinel password from \"requirepass \" (if enabled) in Sentinel configuration\r\n\tSentinelPassword string\r\n\r\n\t// Allows routing read-only commands to the closest master or slave node.\r\n\t// This option only works with NewFailoverClusterClient.\r\n\tRouteByLatency bool\r\n\t// Allows routing read-only commands to the random master or slave node.\r\n\t// This option only works with NewFailoverClusterClient.\r\n\tRouteRandomly bool\r\n\r\n\t// Route all commands to slave read-only nodes.\r\n\tSlaveOnly bool\r\n\r\n\t// Use slaves disconnected with master when cannot get connected slaves\r\n\t// Now, this option only works in RandomSlaveAddr function.\r\n\tUseDisconnectedSlaves bool\r\n\r\n\t// Following options are copied from Options struct.\r\n\r\n\tDialer func(ctx context.Context, network, addr string) (net.Conn, error)\r\n\tOnConnect func(ctx context.Context, cn *Conn) error\r\n\r\n\tUsername string\r\n\tPassword string\r\n\tDB int\r\n\r\n\tMaxRetries int\r\n\tMinRetryBackoff time.Duration\r\n\tMaxRetryBackoff time.Duration\r\n\r\n\tDialTimeout time.Duration\r\n\tReadTimeout time.Duration\r\n\tWriteTimeout time.Duration\r\n\r\n\tPoolSize int\r\n\tMinIdleConns int\r\n\tMaxConnAge time.Duration\r\n\tPoolTimeout time.Duration\r\n\tIdleTimeout time.Duration\r\n\tIdleCheckFrequency time.Duration\r\n\r\n\tTLSConfig *tls.Config\r\n}\r\n```\r\n\r\n## 1.3. 集群模式\r\n```go\r\ntype ClusterOptions struct {\r\n\t// A seed list of host:port addresses of cluster nodes.\r\n\tAddrs []string\r\n\r\n\t// NewClient creates a cluster node client with provided name and options.\r\n\tNewClient func(opt *Options) *Client\r\n\r\n\t// The maximum number of retries before giving up. Command is retried\r\n\t// on network errors and MOVED/ASK redirects.\r\n\t// Default is 3 retries.\r\n\tMaxRedirects int\r\n\r\n\t// Enables read-only commands on slave nodes.\r\n\tReadOnly bool\r\n\t// Allows routing read-only commands to the closest master or slave node.\r\n\t// It automatically enables ReadOnly.\r\n\tRouteByLatency bool\r\n\t// Allows routing read-only commands to the random master or slave node.\r\n\t// It automatically enables ReadOnly.\r\n\tRouteRandomly bool\r\n\r\n\t// Optional function that returns cluster slots information.\r\n\t// It is useful to manually create cluster of standalone Redis servers\r\n\t// and load-balance read/write operations between master and slaves.\r\n\t// It can use service like ZooKeeper to maintain configuration information\r\n\t// and Cluster.ReloadState to manually trigger state reloading.\r\n\tClusterSlots func(context.Context) ([]ClusterSlot, error)\r\n\r\n\t// Following options are copied from Options struct.\r\n\r\n\tDialer func(ctx context.Context, network, addr string) (net.Conn, error)\r\n\r\n\tOnConnect func(ctx context.Context, cn *Conn) error\r\n\r\n\tUsername string\r\n\tPassword string\r\n\r\n\tMaxRetries int\r\n\tMinRetryBackoff time.Duration\r\n\tMaxRetryBackoff time.Duration\r\n\r\n\tDialTimeout time.Duration\r\n\tReadTimeout time.Duration\r\n\tWriteTimeout time.Duration\r\n\r\n\t// PoolSize applies per cluster node and not for the whole cluster.\r\n\tPoolSize int\r\n\tMinIdleConns int\r\n\tMaxConnAge time.Duration\r\n\tPoolTimeout time.Duration\r\n\tIdleTimeout time.Duration\r\n\tIdleCheckFrequency time.Duration\r\n\r\n\tTLSConfig *tls.Config\r\n}\r\n```\r\n\r\n# 2. java-lettuce\r\n## 2.1. 集群模式\r\n- [spring.redis.lettuce.cluster.refresh.adaptive](https://github.com/redis/lettuce/wiki/Redis-Cluster#user-content-refreshing-the-cluster-topology-view):redis集群拓扑自动刷新,默认false\r\n- spring.redis.lettuce.cluster.refresh.period:集群拓扑刷新周期\r\n- spring.redis.lettuce.pool.time-between-eviction-runs:空闲对象逐出器线程的运行间隔时间。当为正值时,空闲对象逐出器线程启动,否则不执行空闲对象逐出。\r\n> [!note]\r\n> The Redis Cluster configuration may change at runtime. New nodes can be added, the master for a specific slot can change. Lettuce handles `MOVED` and `ASK` redirects transparently but in case too many commands run into redirects, you should refresh the cluster topology view. The topology is bound to a `RedisClusterClient` instance. All cluster connections that are created by one `RedisClusterClient` instance share the same cluster topology view.\r\n\r\n故障无法切换的问题可以参考:https://blog.csdn.net/zzhongcy/article/details/107043589"},{"id":"redis命令","title":"Redis命令","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":9,"slug":"redis命令","description":"1. redis支持数据类型 - 字符串 - hash(key-value) - list(有序列表) - set(无序唯一集合) - zset(有序唯一优先级队列,每个元素关联一个分数,按从小到大排列) - Bitmap - HyperLogLog,占用内存很小(12kb)的情况,可以用于估算接近...","relativePath":"Tech/Middleware/redis/redis命令.md","rawContent":"# 1. redis支持数据类型\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250524173043.png)\r\n- 字符串\r\n- hash(key-value)\r\n- list(有序列表)\r\n- set(无序唯一集合)\r\n- zset(有序唯一优先级队列,每个元素关联一个分数,按从小到大排列)\r\n- Bitmap\r\n- HyperLogLog,占用内存很小(12kb)的情况,可以用于**估算**接近$2^{64}-1$个元素的基数(集合去重后的数量)\r\n- SUBSCRIBE与PUBLISH,发布订阅模型\r\n- stream 消息队列\r\n\r\n|数据结构|特点|典型应用场景|\r\n|---|---|---|\r\n|**String**|单值存储|缓存、计数器、分布式锁|\r\n|**List**|有序队列|消息队列、任务调度|\r\n|**Set**|无序不重复|用户标签、去重、共同好友|\r\n|**Sorted Set**|有序集合|排行榜、延时任务|\r\n|**Hash**|字典结构|存储用户信息、购物车|\r\n|**Bitmap**|位操作|签到、活跃用户统计|\r\n|**HyperLogLog**|近似去重|UV 统计、大规模去重|\r\n|**Geo**|地理位置存储|LBS、附近的人|\r\n|**Stream**|日志队列|日志存储、消息队列|\r\n\r\n# 2. 数据库\r\nRedis本身支持16个数据库(0~15),通过 数据库id 设置,默认为0\r\n## 2.1. 切换DB\r\n```shell\r\nselect 0;\r\nselect 10;\r\n```\r\n## 2.2. 清空当前DB数据\r\n```shell\r\nflushdb\r\n# flushdb async\r\n```\r\n## 2.3. 清空当DB数据\r\n```shell\r\nflushall\r\nflushall async\r\n```\r\n# 3. bitmap\r\nRedis Bitmap(位图) 是一种特殊的**String 类型**数据结构,它的本质是一个二进制数组,支持按位(bit)操作,非常适合用于高效存储和查询二进制状态,例如用户签到、活跃用户统计、设备在线状态等。\r\n\r\n在Bitmap中,每个**bit 位**只能是 `0` 或 `1`,可以用来表示 \"是/否\"、\"开/关\" 等布尔状态,且 **存储效率极高**(1 字节可以存储 8 位,1MB可以存储 **8,388,608(约 800 万)个用户状态**)。\r\n\r\n## 3.1. SETBIT \r\n设置某一位的值,`SETBIT key offset value`\r\n- `key`:Bitmap 的键\r\n- `offset`:要设置的位偏移(索引),从 0 开始\r\n- `value`:`0` 或 `1`\r\n\r\n```shell\r\nSETBIT user:1001:sign 0 1 \r\n# 设置第0位为 1 \r\nSETBIT user:1001:sign 1 1 \r\n# 设置第 1 位为 1 \r\nSETBIT user:1001:sign 2 0 \r\n# 设置第 2 位为 0\r\n```\r\n这表示 `user:1001` 在 **第 0 天和第 1 天签到**,第 2 天未签到。\r\n## 3.2. GETBIT\r\n获取某一位的值,`GETBIT key offset`\r\n- `key`:Bitmap 的键\r\n- `offset`:要获取的位偏移(索引)\r\n```shell\r\nGETBIT user:1001:sign 0 \r\n# 返回 1,表示第 0 天已签到 \r\nGETBIT user:1001:sign 2 \r\n# 返回 0,表示第 2 天未签到`\r\n```\r\n\r\n## 3.3. BITCOUNT\r\n统计 Bitmap 中值为1的位,`BITCOUNT key [start end]`,左闭右闭区间\r\n- `key`:Bitmap 的键\r\n- `start`、`end`(可选):字节范围(单位是字节,不是 bit),如果不提供,则统计整个 Bitmap\r\n```shell\r\nBITCOUNT user:1001:sign \r\n# 统计该 Bitmap中1的个数\r\nBITCOUNT user:1001:sign 0 1 \r\n# 统计前两个字节的1的个数\r\n```\r\n假设 `user:1001:sign` 的前 7 位是 `1101010`,`BITCOUNT` 返回 `4`,表示用户这一周签到4天\r\n## 3.4. BITOP\r\n按位运算(AND、OR、XOR、NOT),`BITOP` 允许对多个 Bitmap 进行按位操作,并将结果存储到一个新的键中。`BITOP operation destkey key1 key2 ...`\r\n- `operation`:支持 `AND`(与)、`OR`(或)、`XOR`(异或)、`NOT`(非)\r\n- `destkey`:存储计算结果的 Bitmap\r\n- `key1 key2 ...`:参与计算的 Bitmap\r\n```shell\r\n# 计算 user1 和 user2 的交集(即两个人都签到的天数) \r\nBITOP AND both_signed user:1001:sign user:1002:sign\r\n# 统计共有多少天都签到\r\nBITCOUNT both_signed \r\n```\r\n- `AND`(交集):计算 **多个用户都签到的天数**\r\n- `OR`(并集):计算 **至少有一个用户签到的天数**\r\n- `XOR`(异或):计算 **只签到了一方的天数**\r\n- `NOT`(取反):计算 **未签到的天数**\r\n## 3.5. BITPOS\r\n查找第一个1或0出现的位置,`BITPOS key bit [start end]`\r\n- `bit`:要查找的值(`0` 或 `1`)\r\n- `start end`(可选):字节范围\r\n```shell\r\n# 找到第一个 1 出现的位置 \r\nBITPOS user:1001:sign 1\r\n```\r\n如果 `user:1001:sign` 是 `0001001`,那么 `BITPOS` 返回 `3`(因为第3位是第一个 `1`)。\r\n# 4. stream、发布订阅\r\n✅ 消费者组共享的是整体的消费进度(last-delivered-id),但不同消费者不会共享具体的消费状态。 \r\n✅ 一个消息ID只能被组内一个消费者消费,除非未被 `XACK`确认。 \r\n✅ 如果消费者崩溃或超时,其他消费者可以通过 `XCLAIM` 夺取消息\r\n✅ redis stream不提供类似kafka的多partition机制,多个消费者可以并行消费。如消费者1消费1-100,消费者2消费101-200\r\n\r\n|特性|Redis Stream|发布/订阅(Pub/Sub)|\r\n|---|---|---|\r\n| **消息存储** | 持久化存储(可以读取历史消息) | 不存储消息(消息发布后不会被保留) |\r\n|**消息消费**|多个消费者可以各自独立消费,支持消费组|订阅的客户端实时接收消息,消息不会重复投递|\r\n|**历史数据**|消费者可以读取之前的消息|只能接收发布后的新消息|\r\n|**消息确认**|消费者需要手动确认消费|没有消息确认机制|\r\n|**消费模式**|支持点对点(多个消费者不同分配消息)和广播|纯粹的广播模式|\r\n|**持久化**|支持持久化(基于 AOF 或 RDB 机制)|不支持持久化|\r\n|**典型应用场景**|日志收集、事件溯源、任务队列|实时通知、事件广播|\r\n## 4.1. redis id递增\r\nreids id的设计考虑了时间回拨的情况,为了保证消息是有序的,因此Redis生成的ID是单调递增有序的。由于ID中包含时间戳部分,为了避免服务器时间错误而带来的问题(例如服务器时间延后了),Redis的每个Stream类型数据都维护一个latest_generated_id属性,用于记录最后一个消息的ID。**若发现当前时间戳退后(小于latest_generated_id所记录的),则采用时间戳不变而序号递增的方案来作为新消息ID**(这也是序号为什么使用int64的原因,保证有足够多的的序号),从而保证ID的单调递增性质。\r\n# 5. 查找所有key\r\n## 5.1. scan系列命令\r\n```shell\r\n# 分页查找所有key\r\nSCAN cursor [MATCH pattern] [COUNT count] [TYPE type]\r\n```\r\n示例 \r\n```shell\r\n2025-03-13 23:27:05 set name w\r\nOK\r\n2025-03-13 23:27:11 set age 19\r\nOK\r\n2025-03-13 23:31:32 FLUSHDB\r\nOK\r\n2025-03-13 23:31:38 set name shineri\r\nOK\r\n2025-03-13 23:31:43 set name shinerio\r\nOK\r\n2025-03-13 23:31:48 set age 30\r\nOK\r\n2025-03-13 23:31:53 set address beijing\r\nOK\r\n2025-03-13 23:34:15 hset person jack 20 alice 30\r\n2\r\n2025-03-13 23:34:39 sadd student jack tom mary\r\n3\r\n2025-03-13 23:35:46 zadd teach 1 xiaohong\r\n1\r\n2025-03-13 23:35:54 zadd teach 10 xiaolan\r\n1\r\n2025-03-13 23:36:09 scan 0 count 10\r\n1) \"0\"\r\n2) 1) \"age\"\r\n 1) \"teach\"\r\n 2) \"name\"\r\n 3) \"student\"\r\n 4) \"address\"\r\n 5) \"person\"\r\n2025-03-13 23:37:57 scan 0 match a* count 10\r\n1) \"0\"\r\n2) 1) \"age\"\r\n 1) \"address\"\r\n2025-03-13 23:38:49 scan 0 match *s* count 10 type string\r\n1) \"0\"\r\n2025-03-13 23:41:01 zscan teach 0\r\n1) \"0\"\r\n2) 1) \"xiaohong\"\r\n 1) \"1\"\r\n 2) \"xiaolan\"\r\n 3) \"10\"\r\n3) ) \"address\"\r\n2025-03-13 23:39:29 hscan person 0 count 10\r\n1) \"0\"\r\n2) 1) \"jack\"\r\n 1) \"20\"\r\n 2) \"alice\"\r\n2025-03-13 23:40:38 sscan student 0\r\n1) \"0\"\r\n2) 1) \"mary\"\r\n 1) \"tom\"\r\n 2) \"jack\"\r\n\r\n```\r\n## 5.2. scan与smembers\r\n\r\n| 对比项 | `SMEMBERS` | `SCAN` |\r\n| --------------- | --------------------- | ----------------------- |\r\n| **返回方式** | 一次性返回所有数据 | 迭代返回,分批获取 |\r\n| **适用场景** | **小集合**(几千个以内) | **大集合**(百万/千万级) |\r\n| **是否阻塞** | **阻塞**(影响 Redis 其他操作) | **非阻塞**(分批遍历,不影响 Redis) |\r\n| **查询效率** | 快速获取小集合数据 | **适合大集合,减少网络传输开销** |\r\n| **数据一致性** | 数据不会变化(快照) | 可能在迭代过程中数据发生变化 |\r\n| **支持模式匹配** | ❌ 不支持 | ✅ `MATCH pattern` |\r\n| **可能返回重复数据** | ❌ 不会 | ✅ 可能,需要去重 |\r\n| **COUNT 控制返回量** | ❌ 不支持 | ✅ 支持 `COUNT`(但不保证精确数量) |\r\n\r\n## 5.3. 什么时候用 `SMEMBERS`,什么时候用 `SCAN`?\r\n\r\n- ✅ **集合很小(< 10,000)** 👉 `SMEMBERS` **一次性获取**。\r\n- ✅ **集合很大(> 10,000)** 👉 用 `SCAN` **分批遍历,防止阻塞**。\r\n- ✅ **需要模式匹配(`MATCH`)** 👉 用 `SCAN`(`SMEMBERS` **不支持** `MATCH`)。\r\n- ✅ **不希望有重复数据,想一次拿全量数据** 👉 用 `SMEMBERS`(`SCAN` 可能会返回重复项)。\r\n## 5.4. 总结\r\n- `SMEMBERS` 适合**小集合**,但**大集合慎用**,否则会**阻塞 Redis**。\r\n- `SCAN` 适合**大集合**,但可能返回重复数据,需要**自己去重**。\r\n- 如果集合元素**少于 1 万**,可以放心用 `SMEMBERS`;**超过 1 万**,建议用 `SCAN` 逐步遍历。\r\n# 6. ref\r\n[redis命令行文档](https://redis.io/docs/latest/commands/)"},{"id":"redis批处理","title":"Redis批处理","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":20,"slug":"redis批处理","description":"1. pipeline Redis执行一条命令需要经历以下过程:、、、。由于Redis本身是基于协议(停等机制)的,虽然Redis已经提供了像 、 这种批量的命令,但是如果某些操作根本就不支持或没有批量的操作或者需要连续执行好几个不同命令,那我们就只能一条一条地执行命令,每执行一条命令都要消耗请求与...","relativePath":"Tech/Middleware/redis/redis批处理.md","rawContent":"# 1. pipeline\r\nRedis执行一条命令需要经历以下过程:`发送命令`、`命令排队`、`命令执行`、`结果响应`。由于Redis本身是基于`Request/Response`协议(停等机制)的,虽然Redis已经提供了像 `mget` 、`mset` 这种批量的命令,但是如果某些操作根本就不支持或没有批量的操作或者需要连续执行好几个不同命令,那我们就只能一条一条地执行命令,每执行一条命令都要消耗请求与响应的时间,性能就会大大损耗。Redis中通过`Pipeline`机制能改善上面这类问题,**它能将一组Redis 命令进行组装,通过一次传输给 Redis 并返回结果集**。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250507220852.png)\r\npipeline的方式相对于非pipeline的方式在执行多条命令的情况下性能会显著提升,网络环境越差,执行命令数量越多,提升越大,可以通过如下测试查看\r\n```python\r\nimport redis\r\nimport time\r\n\r\nr = redis.from_url(\"redis://default:tlq8mzql@dbconn.sealoshzh.site:41787\")\r\npipe = r.pipeline()\r\nstart = int(1000 * time.time())\r\nfor i in range(0, 10000):\r\n pipe.set('key1', 'value1')\r\nresults = pipe.execute()\r\nprint(\"time: %dms\" % (int(1000 * time.time()) - start))\r\nstart = int(1000 * time.time())\r\nfor i in range(0, 10000):\r\n r.execute_command(\"set key1 value1\")\r\nprint(\"time: %dms\" % (int(1000 * time.time()) - start))\r\n\r\n> time: 126ms\r\n> time: 43839ms\r\n```\r\n\r\n> [!note]\r\n> - `Pipeline`是非原子的,Redis实际上还是一条一条的执行的,而执行命令是需要排队执行的,所以就会出现原子性问题。\r\n> - `Pipeline`中包含的命令不要包含过多\r\n> - `Pipeline`每次只能作用在一个 Redis 节点上,redis集群化部署的时候,需要确保pipeline中执行的所有命令的key相同,或者将不同的key按照所属redis node拆分到不同的pipeline中\r\n\r\n# 2. 原子性\r\n先说结论,redis的原子性仅保证批量执行当前命令的时候,不会有其他任务或线程能够执行redis命令,也就是保证了不会并发,但是如果执行多个命令,其中部分命令失败不会触发回滚,也不会影响后续的命令的执行。\r\n\r\nredis原子执行命令有两种方式:1. multi+exec 2. lua脚本\r\n## 2.1. multi + exec\r\n### 2.1.1. 语法错误\r\n```shell\r\ntest-db-redis.ns-t5f2m76w.svc:6379> multi\r\nOK\r\ntest-db-redis.ns-t5f2m76w.svc:6379(TX)> set name shinerio\r\nQUEUED\r\ntest-db-redis.ns-t5f2m76w.svc:6379(TX)> error_commnd\r\n(error) ERR unknown command 'error_commnd', with args beginning with: \r\ntest-db-redis.ns-t5f2m76w.svc:6379(TX)> set age 30\r\nQUEUED\r\ntest-db-redis.ns-t5f2m76w.svc:6379(TX)> exec\r\n(error) EXECABORT Transaction discarded because of previous errors.\r\ntest-db-redis.ns-t5f2m76w.svc:6379> get name\r\n(nil)\r\ntest-db-redis.ns-t5f2m76w.svc:6379> get age\r\n(nil)\r\n```\r\nerror_command不是一个合法命令,在命令入队时就会立刻提示,使用exec执行命令会直接报错,任何命令都不会生效\r\n### 2.1.2. 运行时错误\r\n```shell\r\ntest-db-redis.ns-t5f2m76w.svc:6379> multi\r\nOK\r\ntest-db-redis.ns-t5f2m76w.svc:6379(TX)> set name shinerio\r\nQUEUED\r\ntest-db-redis.ns-t5f2m76w.svc:6379(TX)> lpush name shinerio1 shinerio2\r\nQUEUED\r\ntest-db-redis.ns-t5f2m76w.svc:6379(TX)> set age 30\r\nQUEUED\r\ntest-db-redis.ns-t5f2m76w.svc:6379(TX)> exec\r\n1) OK\r\n2) (error) WRONGTYPE Operation against a key holding the wrong kind of value\r\n3) OK\r\ntest-db-redis.ns-t5f2m76w.svc:6379> get name\r\n\"shinerio\"\r\ntest-db-redis.ns-t5f2m76w.svc:6379> get age\r\n\"30\"\r\n```\r\n运行错误是指输入的指令格式正确,但是在命令执行期间出现的错误,典型场景是当输入参数的数据类型不符合命令的参数要求时,就会发生运行错误。例如下面的例子中,对一个string类型的值执行列表的操作,就会报错。但是除执行中出现错误的命令外,其他命令都能正常执行。\r\n> [!note]\r\n> 通过分析我们知道了redis中的事务是不满足原子性的,在运行错误的情况下,并没有提供类似数据库中的回滚功能。那么为什么redis不支持回滚呢,官方文档给出了说明,大意如下:\r\n> - redis命令失败只会发生在语法错误或数据类型错误的情况,这一结果都是由编程过程中的错误导致,这种情况应该在开发环境中检测出来,而不是生产环境\r\n> - 不使用回滚,能使redis内部设计更简单,速度更快\r\n> - 回滚不能避免编程逻辑中的错误,如果想要将一个键的值增加2却只增加了1,这种情况即使提供回滚也无法提供帮助\r\n## 2.2. lua\r\n最常用的EVAL用于执行一段脚本,它的命令的格式如下:\r\n```shell\r\nEVAL script numkeys key [key ...] arg [arg ...]  \r\n```\r\n简单解释一下其中的参数:\r\n- script是一段lua脚本程序\r\n- numkeys指定后续参数有几个key,如没有key则为0\r\n- key [key …]表示脚本中用到的redis中的键,在lua脚本中通过KEYS[i]的形式获取\r\n- arg [arg …]表示附加参数,在lua脚本中通过ARGV[i]获取\r\n看一个简单的例子:\r\n```shell\r\n127.0.0.1:6379> eval \"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\" 2 key1 key2 value1 vauel2 \r\n1) \"key1\" \r\n2) \"key2\" \r\n3) \"value1\" \r\n4) \"vauel2\" \r\n```\r\n在上面的命令中,双引号中是lua脚本程序,后面的2表示存在两个key,分别是key1和key2,之后的参数是附加参数value1和value2。\r\n\r\n如果想要使用lua脚本执行set命令,可以写成这样:\r\n```shell\r\n127.0.0.1:6379> EVAL \"redis.call('SET', KEYS[1], ARGV[1]);\" 1 name Hydra \r\n(nil) \r\n```\r\n这里使用了redis内置的lua函数redis.call来完成set命令,这里打印的执行结果nil是因为没有返回值,如果不习惯的话,其实我们可以在脚本中添加return 0;的返回语句。\r\n### 2.2.1. SCRIPT LOAD 和 EVALSHA命令\r\n这两个命令放在一起是因为它们一般成对使用。先看SCRIPT LOAD,它用于把脚本加载到缓存中,返回SHA1校验和,这时候只是缓存了命令,但是命令没有被马上执行,看一个例子:\r\n```\r\n127.0.0.1:6379> SCRIPT LOAD \"return redis.call('GET', KEYS[1]);\"\r\n\"228d85f44a89b14a5cdb768a29c4c4d907133f56\" \r\n```\r\n这里返回了一个SHA1的校验和,接下来就可以使用EVALSHA来执行脚本了:\r\n```\r\n127.0.0.1:6379> EVALSHA \"228d85f44a89b14a5cdb768a29c4c4d907133f56\" 1 name \r\n\"shinerio\" \r\n```\r\n这里使用这个SHA1值就相当于导入了上面缓存的命令,在之后再拼接numkeys、key、arg等参数,命令就能够正常执行了。\r\n### 2.2.2. 其他命令\r\n使用SCRIPT EXISTS命令判断脚本是否被缓存:\r\n```\r\n127.0.0.1:6379> SCRIPT EXISTS 228d85f44a89b14a5cdb768a29c4c4d907133f56 \r\n1) (integer) 1 \r\n```\r\n使用SCRIPT FLUSH命令清除redis中的lua脚本缓存:\r\n```\r\n127.0.0.1:6379> SCRIPT FLUSH \r\nOK \r\n127.0.0.1:6379> SCRIPT EXISTS 228d85f44a89b14a5cdb768a29c4c4d907133f56 \r\n1) (integer) 0 \r\n```\r\n可以看到,执行了SCRIPT FLUSH后,再次通过SHA1值查看脚本时已经不存在。最后,还可以使用SCRIPT KILL命令杀死当前正在运行的 lua 脚本,但是只有当脚本没有执行写操作时才会生效。\r\n从这些操作看来,lua脚本具有下面的优点:\r\n- 多次网络请求可以在一次请求中完成,减少网络开销,减少了网络延迟\r\n- 客户端发送的脚本会存在redis中,其他客户端可以复用这一脚本,而不需要再重复编码完成相同的逻辑\r\n### 2.2.3. Java代码中使用lua脚本\r\n在Java代码中可以使用Jedis中封装好的API来执行lua脚本,下面是一个使用Jedis执行lua脚本的例子:\r\n```java\r\npublic static void main(String[] args) { \r\n    Jedis jedis = new Jedis(\"127.0.0.1\", 6379); \r\n    String script=\"redis.call('SET', KEYS[1], ARGV[1]);\" \r\n            +\"return redis.call('GET', KEYS[1]);\"; \r\n    List keys= Arrays.asList(\"age\"); \r\n    List values= Arrays.asList(\"eighteen\"); \r\n    Object result = jedis.eval(script, keys, values); \r\n    System.out.println(result); \r\n} \r\n```\r\n执行上面的代码,控制台打印了get命令返回的结果:\r\n```\r\neighteen \r\n```\r\n简单的铺垫完成后,我们来看一下lua脚本究竟能否实现回滚级别的原子性。对上面的代码进行改造,插入一条运行错误的命令:\r\n```java\r\npublic static void main(String[] args) { \r\n    Jedis jedis = new Jedis(\"127.0.0.1\", 6379); \r\n    String script=\"redis.call('SET', KEYS[1], ARGV[1]);\" \r\n            +\"redis.call('INCR', KEYS[1]);\" \r\n            +\"return redis.call('GET', KEYS[1]);\"; \r\n    List keys= Arrays.asList(\"age\"); \r\n    List values= Arrays.asList(\"eighteen\"); \r\n    Object result = jedis.eval(script, keys, values); \r\n    System.out.println(result); \r\n} \r\n```\r\n查看执行结果:\r\n[![](https://s2.51cto.com/oss/202109/08/6ad6b25e76bf821c1106e8c9c35bb0fc.png)](https://s2.51cto.com/oss/202109/08/6ad6b25e76bf821c1106e8c9c35bb0fc.png)\r\n再到客户端执行一下get命令:\r\n```\r\n127.0.0.1:6379> get age \r\n\"eighteen\" \r\n```\r\n也就是说,虽然程序抛出了异常,但异常前的命令还是被正常的执行了且没有被回滚。再试试直接在redis客户端中运行这条指令:\r\n```shell\r\ntest-db-redis.ns-t5f2m76w.svc:6379> eval \"redis.call('SET', KEYS[1], ARGV[1]);redis.call('INCR', KEYS[1]);redis.call('SET', KEYS[2], ARGV[2])\" 2 name age shinerio1 31\r\n(error) ERR value is not an integer or out of range script: c0a1a8d54c17320de3688942a347d37d79b34ca7, on @user_script:1.\r\ntest-db-redis.ns-t5f2m76w.svc:6379> get name\r\n\"shinerio1\"\r\ntest-db-redis.ns-t5f2m76w.svc:6379> get age\r\n(nil)\r\n```\r\n\r\n同样,错误之前的指令仍然没有被回滚,在redis中是使用的同一个lua解释器来执行所有命令,也就保证了当一段lua脚本在执行时,不会有其他脚本或redis命令同时执行,保证了操作不会被其他指令插入或打扰,实现的仅仅是这种程度上的原子操作。遗憾的是,如果**lua脚本运行时出错并中途结束,之后的操作不会进行,之前已经发生的写操作也不会撤销**,所以即使使用了lua脚本,也不能实现类似数据库回滚的原子性。\r\n# 3. redis消费组\r\nRedis会将流中的消息动态分配给消费者组中的各个消费者,确保每条消息只被一个消费者处理。消费者处理完消息后需要显式确认(XACK 命令),Redis 会记录每个消息的处理状态。\r\n**分配算法:**\r\n- Redis 使用\"先到先得\"原则,消费者通过XREADGROUP命令主动获取新消息\r\n- 获取消息时,Redis 会将尚未分配给其他消费者的消息分配给请求的消费者\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250510230122.png)\r\n- 每个Redis Stream都有唯一的名称 ,对应唯一的Redis Key 。\r\n- 同一个Stream可以挂载多个**消费组ConsumerGroup** , 消费组不能自动创建,需要**使用XGROUP CREATE命令创建**。\r\n- 每个消费组会有个**游标 last_delivered_id**,任意一个消费者**读取**了(无需ack)消息都会使游标 last_delivered_id往前移动 ,标识当前消费组消费到哪条消息了。\r\n- 消费组ConsumerGroup同样可以挂载多个消费者Consumer , 每个Consumer并行的读取消息,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。\r\n- 消费者内部有一个属性**pending_ids** , 记录了当前消费者读取但没有回复ACK的消息ID列表 。\r\n> [!note]\r\nredis没有kafka那种partition局部保序的能力,应用程序可以自行创建多个stream实现。\r\n## 3.1. 核心命令\r\n### 3.1.1. 添加消息\r\n```shell\r\nxadd key [NOMKSTREAM] [MAXLEN|MINID [=|~] threshold [LIMIT count]] *|id field value [field value ...]\r\n```\r\n- key:redis stream名称\r\n- NOMKSTREAM:当流不存在时,阻止自动创建流。如果流不存在,命令会返回 `(nil)`。\r\n- MAXLEN:限制流的最大消息数量,超出时自动删除旧消息\r\n\t- \"=\" 精确截断,严格保持消息数量不超过阈值\r\n\t- \"~\" 近似截断(推荐),允许少量超过阈值以提高性能。\r\n\t- threshold:阈值\r\n\t- `LIMIT count`:限制每次删除的最大消息数(Redis 6.2+)。\r\n\t- `*|id`:自定义id,必须大于流中已有最大 ID,否则会报错;自动生成,格式为 `<毫秒时间戳>-<序列号>`(推荐)\r\n- `MINID`: 删除 ID 小于指定值的消息,保留较新的消息\r\n- `field value [field value ...]`: 消息的字段和值,类似哈希表结构。\r\n```shell\r\nxadd mystream MAXLEN ~ 1000 * name \"Eve\" age 25 city \"London\"\r\n```\r\n> [!note]\r\n> 此命令添加了**一条**包含**多个字段**的消息\r\n\r\npython代码读取stream示例如下\r\n```python\r\nimport redis\r\nimport time\r\n\r\n# 连接 Redis\r\nr = redis.Redis(host='localhost', port=6379, db=0)\r\nstream_key = 'mystream'\r\n\r\n# 初始 ID 设为 0,表示从第一条消息开始获取\r\nlast_id = '0'\r\n\r\nwhile True:\r\n # 阻塞读取,timeout=0 表示永久阻塞,直到有新消息\r\n messages = r.xread({stream_key: last_id}, count=10, block=5000)\r\n \r\n if not messages:\r\n print(\"没有新消息,继续等待...\")\r\n continue\r\n \r\n # 处理消息\r\n for stream, entries in messages:\r\n for message_id, fields in entries:\r\n print(f\"收到消息 ID: {message_id}, 内容: {fields}\")\r\n \r\n # 更新 last_id 为当前消息的 ID,下次从下一条消息开始获取\r\n last_id = message_id\r\n \r\n # 模拟处理耗时\r\n time.sleep(0.1)\r\n```\r\n输出如下\r\n```\r\n收到消息 ID: b'1746885666676-0', 内容: {b'name': b'Eve', b'age': b'25', b'city': b'London'}\r\n```\r\n### 3.1.2. 获取指定消息列表\r\n```shell\r\nxrange key start end [COUNT count]\r\n```\r\n- **start** :开始值, **-** 表示最小值\r\n- **end** :结束值, **+** 表示最大值\r\n- **count** :数量\r\n```shell\r\nxrange mystream - +\r\n1) 1) \"1746885666676-0\"\r\n 2) 1) \"name\"\r\n 2) \"Eve\"\r\n 3) \"age\"\r\n 4) \"25\"\r\n 5) \"city\"\r\n 6) \"London\"\r\n```\r\n### 3.1.3. XREAD 以阻塞/非阻塞方式获取消息列表\r\n```shell\r\nxread [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]\r\n```\r\n- **count** :数量\r\n- **milliseconds** :可选,阻塞毫秒数,没有设置就是非阻塞模式\r\n- **key** :队列名\r\n- **id** :id**大于**指定消息ID的消息才会被读取\r\n```shell\r\n127.0.0.1:6379> XREAD block 1000 streams mystream $\r\n(nil)\r\n(1.07s)\r\n```\r\n使用 Block 模式,配合` 文章列表 - Shinerio's Blog 作为ID ,表示读取最新的消息,若没有消息,命令阻塞;等待过程中,其他客户端向队列追加消息,则会立即读取到。因此,典型的队列就是XADD配合 XREAD Block完成。XADD负责生成消息,XREAD负责消费消息。\r\n```shell\r\nxread streams mystream 0-0\r\n1) 1) \"mystream\"\r\n 2) 1) 1) \"1746885666676-0\"\r\n 2) 1) \"name\"\r\n 2) \"Eve\"\r\n 3) \"age\"\r\n 4) \"25\"\r\n 5) \"city\"\r\n 6) \"London\"\r\n 2) 1) \"1746886506263-0\"\r\n 2) 1) \"name\"\r\n 2) \"Shinerio\"\r\n 3) \"age\"\r\n 4) \"30\"\r\n 5) \"city\"\r\n```\r\n### 3.1.4. 创建消费者组\r\n```shell\r\nXGROUP CREATE key groupname id|$ [MKSTREAM] [ENTRIESREAD entries_read]\r\n```\r\n - `id| 文章列表 - Shinerio's Blog : 指定消费者组的起始消费位置。\r\n - ` 文章列表 - Shinerio's Blog :从流的末尾开始消费,只处理创建组之后新添加的消息。\r\n - `id`:指定具体的消息 ID,从该 ID 之后开始消费(不包含该 ID 本身)。例如:`0` 表示从流的第一条消息开始消费;`1620000000000-0` 表示从指定 ID 之后开始。\r\n- `MKSTREAM`:如果流不存在,则自动创建流、\r\n- `ENTRIESREAD entries_read`(可选,Redis 6.2+):记录消费者组创建时流的历史消息数量(仅用于统计,不影响消费行为)。\r\n```shell\r\nXGROUP CREATE mystream mygroup 0-0\r\n```\r\n### 3.1.5. 从消费者组读取消息\r\n```shell\r\nxreadgroup GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] id [id ...]\r\n```\r\n- `group`:消费者组名称(需提前用 `XGROUP CREATE` 创建)。\r\n- `consumer`:当前消费者的唯一标识(可动态创建)。\r\n- `key`:流的名称。\r\n- `id`:起始 ID,控制从何处开始读取:\r\n - `>`:只获取从未分配给其他消费者的新消息(推荐)。\r\n - `ID`:处理特定ID之后的消息(包括未确认的历史消息)。例如:`0` 表示处理所有待处理消息(包括历史遗留)。\r\n#### 3.1.5.1. NOACK行为\r\n默认行为(无 `NOACK`),**工作流程**:\r\n1. **读取消息**:使用 `XREADGROUP` 读取消息时,Redis 会自动将消息标记为「已分配」(状态为 Pending)。\r\n2. **处理消息**:消费者处理消息。\r\n3. **手动确认**:处理完成后,必须显式调用 `XACK` 命令确认消息,将其从 PEL(Pending Entries List)中移除。\r\n使用 `NOACK` 选项,**工作流程**\r\n4. **读取消息**:使用 `XREADGROUP NOACK` 读取消息时,Redis**不会**将消息标记为 Pending。\r\n5. **处理消息**:消费者处理消息。\r\n6. **无需手动确认**:系统会假设消息已被即时处理,无需后续确认\r\n**核心区别对比**\r\n\r\n| **对比项** | **默认行为(无 `NOACK`)** | **使用 `NOACK`** |\r\n| ---------------- | -------------------------- | ---------------------------------------------------------------------------- |\r\n| **消息是否自动进入 PEL** | 是,读取后立即进入 PEL(状态为 Pending) | 否,需手动 `XACK` 才会记录到历史 |\r\n| **未确认消息的处理** | 留在 PEL 中,可能被重新分配给其他消费者 | 消息状态不变,不会被重新分配(除非手动 `XCLAIM`) |\r\n| **典型场景** | 需要可靠消费,确保消息至少被处理一次 | 1. 处理非关键性数据,可以接受偶尔的消息丢失
2. 追求更高的处理性能,减少确认带来的开销
3. 实现\"至多一次\"传递语义而非\"至少一次\" |\r\n\r\n### 3.1.6. XACK 消息消费确认\r\n每一个消息都需要单独ack\r\n```shell\r\nxack key group id [id ...]\r\n```\r\n### 3.1.7. XTRIM 限制Stream长度\r\n```shell\r\nxtrim key MAXLEN|MINID [=|~] threshold [LIMIT count]\r\n```\r\n对stream消息进行淘汰,暴露最近的threashold个消息\r\n- MAXLEN:按消息长度淘汰\r\n- MINID:按最小消息ID淘汰\r\n### 3.1.8. 删除消费者组\r\n```shell\r\nXGROUP DELCONSUMER key groupname consumername\r\n```\r\n### 3.1.9. 查看指定stream所有消费者组\r\n```shell\r\nXINFO GROUPS mystream\r\n1) 1) \"name\"\r\n 2) \"mygroup\"\r\n 3) \"consumers\"\r\n 4) (integer) 1\r\n 5) \"pending\"\r\n 6) (integer) 3\r\n 7) \"last-delivered-id\"\r\n 8) \"1746886620493-0\"\r\n 9) \"entries-read\"\r\n 2) (integer) 3\r\n 3) \"lag\"\r\n 4) (integer) 0\r\n```\r\n\r\n| 指标 | 值 | 含义 |\r\n| ------------------- | ------------------- | ----------------------------------------- |\r\n| `name` | `\"mygroup\"` | 消费者组的名称。 |\r\n| `consumers` | `(integer) 1` | 该消费者组当前有1个活跃的消费者。 |\r\n| `pending` | `(integer) 3` | 待处理消息的数量(即 PEL 中已分配但未确认的消息)。 |\r\n| `last-delivered-id` | `\"1746886620493-0\"` | 最后一次分配给该组的消息 ID,标志着消费进度。 |\r\n| `entries-read` | `(integer) 3` | 该组从流中读取的消息总数(自组创建以来)。 |\r\n| `lag` | `(integer) 0` | 未被该组消费的消息数量(`lag = 流总长度 - entries-read`)。 |\r\n```shell\r\ntest-db-redis.ns-12pvbug6.svc:6379> XACK mystream mygroup 1746886620493-0\r\n(integer) 1\r\ntest-db-redis.ns-12pvbug6.svc:6379> XINFO GROUPS mystream\r\n1) 1) \"name\"\r\n 2) \"mygroup\"\r\n 3) \"consumers\"\r\n 4) (integer) 1\r\n 5) \"pending\"\r\n 6) (integer) 2\r\n 7) \"last-delivered-id\"\r\n 8) \"1746886620493-0\"\r\n 9) \"entries-read\"\r\n 2) (integer) 3\r\n 3) \"lag\"\r\n 4) (integer) 0\r\n```\r\n### 3.1.10. 故障检测\r\nRedis通过消费者组的维护机制来检测故障消费者并重新分配pending消息,Redis Stream没有自动的消费者故障检测机制,而是通过以下方式处理:\r\n1. **空闲时间检查**\r\n - Redis记录每个消费者的最后活动时间\r\n - **应用程序负责**主动检查长时间不活跃的消费者\r\n2. **手动声明故障**\r\n - 系统中通常需要有监控组件来检测消费者状态\r\n - 发现消费者故障后,需要**手动声明**该消费者已经失效\r\n#### 3.1.10.1. 重新分配pending消息的方法\r\n1. **使用XPENDING命令查看未确认消息**\r\n\t```shell\r\n\tXPENDING mystream mygroup [start-id end-id count [consumer-name]]\r\n\t```\r\n\t- 查看消费者组中所有未确认的消息\r\n - 可以筛选特定消费者的未确认消息\r\n每个Pending的消息有4个属性:\r\n- 消息ID\r\n- 所属消费者\r\n- IDLE,已读取时长\r\n- delivery counter,消息被读取次数\r\n1. **使用XCLAIM重新分配消息**\r\n ```shell\r\n XCLAIM mystream mygroup new-consumer-name min-idle-time ID1 [ID2 ...]\r\n ```\r\n - 将已被分配但未确认的消息转移给新的消费者\r\n - `min-idle-time`参数指定消息必须处于未确认状态的最小时长,只有超过这个时长,才能被转移\r\n2. **使用XAUTOCLAIM自动认领消息**(Redis 6.2+)\r\n ```shell\r\n XAUTOCLAIM mystream mygroup new-consumer-name min-idle-time start-id [COUNT count]\r\n ```\r\n - 自动认领超过指定空闲时间的消息\r\n - 比XCLAIM更便捷,可一次操作多条消息\r\n```shell\r\nXAUTOCLAIM mystream mygroup myconsumer2 1000 0-0\r\n1) \"0-0\"\r\n2) 1) 1) \"1746885666676-0\"\r\n 2) 1) \"name\"\r\n 2) \"Eve\"\r\n 3) \"age\"\r\n 4) \"25\"\r\n 5) \"city\"\r\n 6) \"London\"\r\n 3) 1) \"1746886506263-0\"\r\n 2) 1) \"name\"\r\n 2) \"Shinerio\"\r\n 3) \"age\"\r\n 4) \"30\"\r\n 5) \"city\"\r\n 6) \"ShangHai\"\r\n4) (empty array)\r\nXINFO GROUPS mystream\r\n5) 1) \"name\"\r\n 2) \"mygroup\"\r\n 3) \"consumers\"\r\n 4) (integer) 2\r\n 5) \"pending\"\r\n 6) (integer) 2\r\n 7) \"last-delivered-id\"\r\n 8) \"1746886620493-0\"\r\n 9) \"entries-read\"\r\n 6) (integer) 3\r\n 7) \"lag\"\r\n 8) (integer) 0\r\nxack mystream mygroup 1746885666676-0 1746886506263-0\r\n(integer) 2\r\nXINFO GROUPS mystream\r\n9) 1) \"name\"\r\n 2) \"mygroup\"\r\n 3) \"consumers\"\r\n 4) (integer) 2\r\n 5) \"pending\"\r\n 6) (integer) 0\r\n 7) \"last-delivered-id\"\r\n 8) \"1746886620493-0\"\r\n 9) \"entries-read\"\r\n 10) (integer) 3\r\n 11) \"lag\"\r\n 12) (integer) 0\r\n``` \r\n\r\n> [!note]\r\n> 在实际应用中,通常需要\r\n> 1. 定期执行检查脚本,识别长时间未活动的消费者\r\n> 2. 检测空闲时间超过预设阈值(如60秒)的消息\r\n> 3. 将这些消息通过XCLAIM或XAUTOCLAIM重新分配给活跃的消费者\r\n\r\n这一机制需要应用程序层面实现,Redis自身不会自动检测故障或重新分配消息。系统需要设计专门的监控和恢复组件来处理这些情况,确保消息不会因消费者故障而丢失。\r\n\r\n"},{"id":"redis高可用","title":"Redis高可用","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"redis高可用","description":"1. master和slave数据复制机制 在Redis集群模式下,Master和Slave之间默认采用异步复制 - 客户端向Master写入数据后,Master立即返回成功响应,无需等待Slave确认。 - 数据随后异步复制到 Slave 节点。 这种设计的目的是保证高性能和可用性,但存在数据丢失...","relativePath":"Tech/Middleware/redis/redis高可用.md","rawContent":"# 1. master和slave数据复制机制\r\n在Redis集群模式下,Master和Slave之间**默认采用异步复制**\r\n- 客户端向Master写入数据后,Master立即返回成功响应,无需等待Slave确认。\r\n- 数据随后异步复制到 Slave 节点。\r\n这种设计的目的是保证高性能和可用性,但存在**数据丢失风险**:如果Master在复制完成前崩溃,未复制到Slave的数据可能丢失。强同步会带来以下问题:\r\n- **性能下降**:等待所有 Slave 确认会显著增加延迟。\r\n- **可用性降低**:只要有一个 Slave 故障,整个集群可能无法写入。\r\n虽然 Redis 集群默认采用异步复制,但在特定场景下可以通过以下方式**近似实现同步复制**\r\n## 1.1. 使用 WAIT 命令(弱同步)\r\nRedis 提供了`WAIT`命令,允许客户端等待数据复制到指定数量的 Slave 后再继续,`WAIT`会等待**当前连接此前执行的所有写命令**(如`SET`、`HSET`、`INCR`等)被复制到至少 N 个 Slave 节点。\r\n```python\r\nimport redis\r\n\r\nr = redis.Redis(host='localhost', port=6379)\r\n\r\n# 执行写操作\r\nr.set('key', 'value')\r\n\r\n# 等待数据复制到至少1个Slave,并设置超时时间(毫秒)\r\nr.execute_command('WAIT', 1, 1000) # 等待1个Slave确认,最多等待1秒\r\n```\r\n\r\n```go\r\nfunc (c cmdable) Wait(ctx context.Context, numSlaves int, timeout time.Duration) *IntCmd {\r\n\tcmd := NewIntCmd(ctx, \"wait\", numSlaves, int(timeout/time.Millisecond))\r\n\t_ = c(ctx, cmd)\r\n\treturn cmd\r\n}\r\n```\r\n**局限性**:\r\n- 仅保证数据复制到 Slave 的内存中,不保证持久化到磁盘。\r\n- 如果所有 Slave 都不可用,`WAIT`会阻塞直到超时。\r\n## 1.2. Allow writes only with N attached replicas\r\n`min-replicas-to-write` 和 `min-replicas-max-lag` 是 Redis **单机模式**(非集群)下的配置参数,用于增强数据安全性:\r\n- **min-replicas-to-write**:至少需要多少个 Slave 同步数据后,Master才接受写操作。\r\n- **min-replicas-max-lag**:Slave与Master的最大延迟(秒),超过此值则认为Slave失效。\r\n**示例配置**:\r\n```conf\r\n# redis.conf(单机模式)\r\nmin-replicas-to-write 1\r\nmin-replicas-max-lag 10\r\n```\r\n\r\nRedis 集群采用 **分片(Sharding)+ 主从复制** 架构,每个主节点(Master)独立管理一部分数据。这种分布式设计导致:\r\n1. **配置复杂性**:每个主节点需单独配置参数,难以统一管理。\r\n2. **可用性权衡**:强制同步会导致单个节点故障影响整个集群写入,违背高可用设计原则。\r\n3. **异步复制优先**:集群默认采用异步复制,追求高性能和可用性。"},{"id":"代码示例","title":"代码示例","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"代码示例","description":"","relativePath":"Tech/Middleware/redis/代码示例.md","rawContent":"```go\r\npackage main\r\n\r\nimport (\r\n\t\"context\"\r\n\t\"fmt\"\r\n\t\"log\"\r\n\t\"time\"\r\n\r\n\t\"github.com/go-redis/redis/v8\"\r\n)\r\n\r\nfunc main() {\r\n\tctx := context.Background()\r\n\r\n\t// 创建Redis客户端\r\n\trdb := redis.NewClient(&redis.Options{\r\n\t\tAddr: \"localhost:6379\",\r\n\t\tDialTimeout: time.Second * 3, // 连接超时时间\r\n\t\tReadTimeout: time.Second * 3, // 读超时时间\r\n\t\tWriteTimeout: time.Second * 3, // 写超时时间\r\n\t\tPoolSize: 3, // 最大连接池大小\r\n\t\tIdleTimeout: time.Second * 3, // 空闲连接的最大存活时间\r\n\t\tMaxConnAge: time.Second * 300, // 连接最大存活时间,无论连接是否空闲\r\n\t\tPoolTimeout: time.Second * 30, // 从连接池中获取连接池超时时间\r\n\t})\r\n\r\n\t// 自动从连接池中获取一个连接,并检查连接\r\n\tif _, err := rdb.Ping(ctx).Result(); err != nil {\r\n\t\tlog.Fatalf(\"无法连接到Redis: %v\", err)\r\n\t}\r\n\r\n\t// 流键和消费者组名称\r\n\tstreamKey := \"mystream\"\r\n\tgroupName := \"mygroup\"\r\n\r\n\t// 创建消费者组(如果不存在)\r\n\t_, err := rdb.XGroupCreateMkStream(ctx, streamKey, groupName, \"$\").Result()\r\n\tif err != nil && err.Error() != \"BUSYGROUP Consumer Group name already exists\" {\r\n\t\tlog.Fatalf(\"创建消费者组失败: %v\", err)\r\n\t}\r\n\r\n\t// 启动多个消费者\r\n\tgo consumer(ctx, rdb, streamKey, groupName, \"consumer-1\", 10)\r\n\tgo consumer(ctx, rdb, streamKey, groupName, \"consumer-2\", 10)\r\n\tgo consumer(ctx, rdb, streamKey, groupName, \"consumer-3\", 2)\r\n\r\n\t// 生产消息\r\n\tgo producer(ctx, rdb, streamKey)\r\n\r\n\t// 保持主程序运行\r\n\tselect {}\r\n}\r\n\r\n// 生产者函数\r\nfunc producer(ctx context.Context, rdb *redis.Client, streamKey string) {\r\n\tfor {\r\n\t\t// 生成消息\r\n\t\tmessage := map[string]interface{}{\r\n\t\t\t\"timestamp\": time.Now().String(),\r\n\t\t\t\"message\": fmt.Sprintf(\"消息 %d\", time.Now().UnixNano()),\r\n\t\t}\r\n\r\n\t\t// 发送消息到流\r\n\t\tid, err := rdb.XAdd(ctx, &redis.XAddArgs{\r\n\t\t\tStream: streamKey,\r\n\t\t\tValues: message,\r\n\t\t}).Result()\r\n\r\n\t\tif err != nil {\r\n\t\t\tlog.Printf(\"发送消息失败: %v\", err)\r\n\t\t} else {\r\n\t\t\tlog.Printf(\"发送消息成功,ID: %s\", id)\r\n\t\t}\r\n\r\n\t\ttime.Sleep(1 * time.Second)\r\n\t}\r\n}\r\n\r\n// 消费者函数\r\nfunc consumer(ctx context.Context, rdb *redis.Client, streamKey, groupName, consumerName string, maxMsg int) {\r\n\tfor i := 0; i < maxMsg; i++ {\r\n\t\t// 从流中读取消息\r\n\t\tstreams, err := rdb.XReadGroup(ctx, &redis.XReadGroupArgs{\r\n\t\t\tGroup: groupName,\r\n\t\t\tConsumer: consumerName,\r\n\t\t\tStreams: []string{streamKey, \">\"}, // \">\" 表示只获取从未分配给其他消费者的新消息\r\n\t\t\tCount: 10, // 每次最多获取10条消息\r\n\t\t\tBlock: 0, // 不阻塞,立即返回\r\n\t\t}).Result()\r\n\r\n\t\tif err != nil {\r\n\t\t\tlog.Printf(\"消费者 %s 读取消息失败: %v\", consumerName, err)\r\n\t\t\ttime.Sleep(1 * time.Second)\r\n\t\t\tcontinue\r\n\t\t}\r\n\r\n\t\t// 处理消息\r\n\t\tfor _, stream := range streams {\r\n\t\t\tfor _, message := range stream.Messages {\r\n\t\t\t\tlog.Printf(\"消费者 %s 收到消息,ID: %s,内容: %v\", consumerName, message.ID, message.Values)\r\n\r\n\t\t\t\t// 模拟处理时间\r\n\t\t\t\ttime.Sleep(200 * time.Millisecond)\r\n\r\n\t\t\t\t// 确认消息处理完成\r\n\t\t\t\tif _, err := rdb.XAck(ctx, streamKey, groupName, message.ID).Result(); err != nil {\r\n\t\t\t\t\tlog.Printf(\"消费者 %s 确认消息失败: %v\", consumerName, err)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tlog.Printf(\"消费者 %s 确认消息成功,ID: %s\", consumerName, message.ID)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// 短暂休眠,避免CPU占用过高\r\n\t\ttime.Sleep(100 * time.Millisecond)\r\n\t}\r\n}\r\n```"},{"id":"发布订阅模式","title":"发布订阅模式","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":11,"slug":"发布订阅模式","description":"Redis发布订阅简介 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis的SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。 作为例子, 下...","relativePath":"Tech/Middleware/redis/发布订阅模式.md","rawContent":"# Redis发布订阅简介\r\nRedis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。\r\n\r\nRedis的SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。\r\n\r\n作为例子, 下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-1.svg)\r\n\r\n当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-2.svg)\r\n\r\n# 发布/订阅使用\r\nRedis有两种发布/订阅模式:\r\n- 基于频道(Channel)的发布/订阅\r\n- 基于模式(pattern)的发布/订阅\r\n\r\n## 基于频道(Channel)的发布/订阅\r\n\"发布/订阅\"模式包含两种角色,分别是发布者和订阅者。发布者可以向指定的频道(channel)发送消息; 订阅者可以订阅一个或者多个频道(channel),所有订阅此频道的订阅者都会收到此消息。\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-8.png)\r\n\r\n* **发布者发布消息**\r\n发布者发布消息的命令是 publish,用法是 publish channel message,如向 channel1.1说一声hi\r\n```\r\n127.0.0.1:6379> publish channel:1 hi\r\n(integer) 1 \r\n```\r\n\r\n这样消息就发出去了。返回值表示接收这条消息的订阅者数量。发出去的消息不会被持久化,也就是有客户端订阅channel:1后只能接收到后续发布到该频道的消息,之前的就接收不到了。\r\n\r\n* **订阅者订阅频道**\r\n\r\n订阅频道的命令是 subscribe,可以同时订阅多个频道,用法是 subscribe channel1 \\[channel2 ...\\],例如新开一个客户端订阅上面频道:(不会收到消息,因为不会收到订阅之前就发布到该频道的消息)\r\n\r\n```\r\n127.0.0.1:6379> subscribe channel:1\r\nReading messages... (press Ctrl-C to quit)\r\n1) \"subscribe\" // 消息类型\r\n2) \"channel:1\" // 频道\r\n3) \"hi\" // 消息内容 \r\n```\r\n\r\n执行上面命令客户端会进入订阅状态,处于此状态下客户端不能使用除`subscribe`、`unsubscribe`、`psubscribe`和`punsubscribe`这四个属于\"发布/订阅\"之外的命令,否则会报错。\r\n\r\n进入订阅状态后客户端可能收到3种类型的回复。每种类型的回复都包含3个值,第一个值是消息的类型,根据消类型的不同,第二个和第三个参数的含义可能不同。\r\n\r\n消息类型的取值可能是以下3个:\r\n\r\n* subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个是当前客户端订阅的频道数量。\r\n* message。表示接收到的消息,第二个值表示产生消息的频道名称,第三个值是消息的内容。\r\n* unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非\"发布/订阅\"模式的命令了。\r\n## 基于模式(pattern)的发布/订阅\r\n\r\n如果有某个/某些模式和这个频道匹配的话,那么所有订阅这个/这些频道的客户端也同样会收到信息。\r\n\r\n* **用图例解释什么是基于模式的发布订阅**\r\n\r\n下图展示了一个带有频道和模式的例子, 其中 tweet.shop.\\* 模式匹配了 tweet.shop.kindle 频道和 tweet.shop.ipad 频道, 并且有不同的客户端分别订阅它们三个:\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-5.svg)\r\n\r\n当有信息发送到 tweet.shop.kindle 频道时, 信息除了发送给 clientX 和 clientY 之外, 还会发送给订阅 tweet.shop.\\* 模式的 client123 和 client256 :\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-6.svg)\r\n\r\n另一方面, 如果接收到信息的是频道 tweet.shop.ipad , 那么 client123 和 client256 同样会收到信息:\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-7.svg)\r\n\r\n* **基于模式的例子**\r\n\r\n通配符中?表示1个占位符,\\*表示任意个占位符(包括0),?\\*表示1个以上占位符。\r\n\r\npublish发布\r\n\r\n```\r\n127.0.0.1:6379> publish c m1\r\n(integer) 0\r\n127.0.0.1:6379> publish c1 m1\r\n(integer) 1\r\n127.0.0.1:6379> publish c11 m1\r\n(integer) 0\r\n127.0.0.1:6379> publish b m1\r\n(integer) 1\r\n127.0.0.1:6379> publish b1 m1\r\n(integer) 1\r\n127.0.0.1:6379> publish b11 m1\r\n(integer) 1\r\n127.0.0.1:6379> publish d m1\r\n(integer) 0\r\n127.0.0.1:6379> publish d1 m1\r\n(integer) 1\r\n127.0.0.1:6379> publish d11 m1\r\n(integer) 1 \r\n```\r\n\r\npsubscribe订阅\r\n\r\n```\r\n127.0.0.1:6379> psubscribe c? b* d?*\r\nReading messages... (press Ctrl-C to quit)\r\n1) \"psubscribe\"\r\n2) \"c?\"\r\n3) (integer) 1\r\n1) \"psubscribe\"\r\n2) \"b*\"\r\n3) (integer) 2\r\n1) \"psubscribe\"\r\n2) \"d?*\"\r\n3) (integer) 3\r\n1) \"pmessage\"\r\n2) \"c?\"\r\n3) \"c1\"\r\n4) \"m1\"\r\n1) \"pmessage\"\r\n2) \"b*\"\r\n3) \"b\"\r\n4) \"m1\"\r\n1) \"pmessage\"\r\n2) \"b*\"\r\n3) \"b1\"\r\n4) \"m1\"\r\n1) \"pmessage\"\r\n2) \"b*\"\r\n3) \"b11\"\r\n4) \"m1\"\r\n1) \"pmessage\"\r\n2) \"d?*\"\r\n3) \"d1\"\r\n4) \"m1\"\r\n1) \"pmessage\"\r\n2) \"d?*\"\r\n3) \"d11\"\r\n4) \"m1\" \r\n```\r\n\r\n* **注意点**\r\n\r\n(1)使用psubscribe命令可以重复订阅同一个频道,如客户端执行了`psubscribe c? c?*`。这时向c1发布消息客户端会接受到两条消息,而同时publish命令的返回值是2而不是1。同样的,如果有另一个客户端执行了`subscribe c1` 和`psubscribe c?*`的话,向c1发送一条消息该客户顿也会受到两条消息(但是是两种类型:message和pmessage),同时publish命令也返回2.\r\n\r\n(2)punsubscribe命令可以退订指定的规则,用法是: `punsubscribe [pattern [pattern ...]]`,如果没有参数则会退订所有规则。\r\n\r\n(3)使用punsubscribe只能退订通过psubscribe命令订阅的规则,不会影响直接通过subscribe命令订阅的频道;同样unsubscribe命令也不会影响通过psubscribe命令订阅的规则。另外需要注意punsubscribe命令退订某个规则时不会将其中的通配符展开,而是进行严格的字符串匹配,所以`punsubscribe *` 无法退订`c*`规则,而是必须使用`punsubscribe c*`才可以退订。(它们是相互独立的,后文可以看到数据结构上看也是两种实现)\r\n\r\n# 深入理解\r\n\r\n\r\n> 我们通过几个问题,来深入理解Redis的订阅发布机制\r\n\r\n## 基于频道(Channel)的发布/订阅如何实现的?\r\n\r\n底层是通过字典(图中的pubsub\\_channels)实现的,这个字典就用于保存订阅频道的信息:字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。\r\n\r\n* **数据结构**\r\n\r\n比如说,在下图展示的这个 pubsub\\_channels 示例中, client2 、 client5 和 client1 就订阅了 channel1 , 而其他频道也分别被别的客户端所订阅:\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-3.svg)\r\n\r\n* **订阅**\r\n\r\n当客户端调用 SUBSCRIBE 命令时, 程序就将客户端和要订阅的频道在 pubsub\\_channels 字典中关联起来。\r\n\r\n举个例子,如果客户端 client10086 执行命令 `SUBSCRIBE channel1 channel2 channel3` ,那么前面展示的 pubsub\\_channels 将变成下面这个样子:\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-4.svg)\r\n\r\n* **发布**\r\n\r\n当调用 `PUBLISH channel message` 命令, 程序首先根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端。\r\n\r\n比如说,对于以下这个 pubsub\\_channels 实例, 如果某个客户端执行命令 `PUBLISH channel1 \"hello moto\"` ,那么 client2 、 client5 和 client1 三个客户端都将接收到 \"hello moto\" 信息:\r\n\r\n* **退订**\r\n\r\n使用 UNSUBSCRIBE 命令可以退订指定的频道, 这个命令执行的是订阅的反操作: 它从 pubsub\\_channels 字典的给定频道(键)中, 删除关于当前客户端的信息, 这样被退订频道的信息就不会再发送给这个客户端。\r\n\r\n## 基于模式(Pattern)的发布/订阅如何实现的?\r\n\r\n底层是pubsubPattern节点的链表。\r\n\r\n* **数据结构** redisServer.pubsub\\_patterns 属性是一个链表,链表中保存着所有和模式相关的信息:\r\n\r\n```\r\nstruct redisServer {\r\n \r\n list *pubsub_patterns;\r\n \r\n}; \r\n```\r\n\r\n链表中的每个节点都包含一个 redis.h/pubsubPattern 结构:\r\n\r\n```\r\ntypedef struct pubsubPattern {\r\n redisClient *client;\r\n robj *pattern;\r\n} pubsubPattern; \r\n```\r\n\r\nclient 属性保存着订阅模式的客户端,而 pattern 属性则保存着被订阅的模式。\r\n\r\n每当调用 PSUBSCRIBE 命令订阅一个模式时, 程序就创建一个包含客户端信息和被订阅模式的 pubsubPattern 结构, 并将该结构添加到 redisServer.pubsub\\_patterns 链表中。\r\n\r\n作为例子,下图展示了一个包含两个模式的 pubsub\\_patterns 链表, 其中 client123 和 client256 都正在订阅 tweet.shop.\\* 模式:\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-9.svg)\r\n\r\n* **订阅**\r\n\r\n如果这时客户端 client10086 执行 `PSUBSCRIBE broadcast.list.*` , 那么 pubsub\\_patterns 链表将被更新成这样:\r\n\r\n![](https://pdai.tech/images/db/redis/db-redis-sub-10.svg)\r\n\r\n通过遍历整个 pubsub\\_patterns 链表,程序可以检查所有正在被订阅的模式,以及订阅这些模式的客户端。\r\n\r\n* **发布**\r\n\r\n发送信息到模式的工作也是由 PUBLISH 命令进行的, 显然就是匹配模式获得Channels,然后再把消息发给客户端。\r\n\r\n* **退订**\r\n\r\n使用 PUNSUBSCRIBE 命令可以退订指定的模式, 这个命令执行的是订阅模式的反操作: 程序会删除 redisServer.pubsub\\_patterns 链表中, 所有和被退订模式相关联的 pubsubPattern 结构, 这样客户端就不会再收到和模式相匹配的频道发来的信息。\r\n\r\n# SpringBoot结合Redis发布/订阅实例?\r\n\r\n最佳实践是通过RedisTemplate,关键代码如下:\r\n\r\n```\r\n redisTemplate.convertAndSend(\"my_topic_name\", \"message_content\");\r\n\r\nRedisMessageListenerContainer container = new RedisMessageListenerContainer();\r\ncontainer.setConnectionFactory(connectionFactory);\r\ncontainer.addMessageListener(xxxMessageListenerAdapter, \"my_topic_name\"); \r\n```\r\n\r\n我找了[一篇文章在新窗口打开](https://blog.csdn.net/llll234/article/details/80966952),如果需要可以看看具体的集成。\r\n\r\n参考文章\r\n----\r\n\r\n* https://redisbook.readthedocs.io/en/latest/feature/pubsub.html\r\n* https://www.cnblogs.com/qlqwjy/p/9763754.html\r\n* https://blog.csdn.net/ibigboy/article/details/95751542"},{"id":"zookeeper","title":"Zookeeper","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"zookeeper","description":"","relativePath":"Tech/Middleware/zookeeper/zookeeper.md","rawContent":""},{"id":"分布式锁","title":"分布式锁","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":11,"slug":"分布式锁","description":"1. 分布式公平锁 ZooKeeper的临时顺序节点,天生就有一副实现分布式锁的胚子。 1. ZooKeeper的每一个节点,都是一个天然的顺序发号器。 在每一个节点下面创建临时顺序节点(EPHEMERAL\\SEQUENTIAL)类型,新的子节点后面,会加上一个次序编号,而这个生成的次序编号,是上一...","relativePath":"Tech/Middleware/zookeeper/分布式锁.md","rawContent":"# 1. 分布式公平锁\r\nZooKeeper的临时顺序节点,天生就有一副实现分布式锁的胚子。\r\n1. ZooKeeper的每一个节点,都是一个**天然的顺序发号器**。 在每一个节点下面创建临时顺序节点(EPHEMERAL\\_SEQUENTIAL)类型,新的子节点后面,会加上一个次序编号,而这个生成的次序编号,是上一个生成的次序编号加一。 例如,有一个用于发号的节点“/test/lock”为父亲节点,可以在这个父节点下面创建相同前缀的临时顺序子节点,假定相同的前缀为“/test/lock/seq-”。第一个创建的子节点基本上应该为/test/lock/seq-0000000000,下一个节点则为/test/lock/seq-0000000001,依次类推。 \r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506122311883.png)\r\n\r\n2. ZooKeeper节点的递增有序性,可以确保锁的公平 一个ZooKeeper分布式锁,首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型),然后每个要获得锁的线程,都在这个节点下创建个临时顺序节点。由于ZK节点,是按照创建的次序,依次递增的。 为了确保公平,可以简单的规定:编号最小的那个节点,表示获得了锁。所以,每个线程在尝试占用锁之前,首先判断自己是排号是不是当前最小,如果是,则获取锁。 \r\n3. ZooKeeper的节点监听机制,可以保障占有锁的传递有序而且高效。每个线程抢占锁之前,先尝试创建自己的ZNode。同样,释放锁的时候,就需要删除创建的Znode。创建成功后,如果不是排号最小的节点,就处于等待通知的状态。等谁的通知呢?不需要其他人,只需要等前一个Znode 的通知就可以了。前一个Znode删除的时候,会触发Znode事件,当前节点能监听到删除事件,就是轮到了自己占有锁的时候。第一个通知第二个、第二个通知第三个,击鼓传花似的依次向后。 ZooKeeper的节点监听机制,能够非常完美地实现这种击鼓传花似的信息传递。具体的方法是,每一个等通知的Znode节点,只需要监听(linsten)或者监视(watch)排号在自己前面那个,而且紧挨在自己前面的那个节点,就能收到其删除事件了。 只要上一个节点被删除了,就进行再一次判断,看看自己是不是序号最小的那个节点,如果是,自己就获得锁。\r\n# 2. 锁异常释放\r\nZooKeeper的内部优越的机制,能保证由于网络异常或者其他原因,集群中占用锁的客户端失联时,锁能够被有效释放。一旦占用Znode锁的客户端与ZooKeeper集群服务器失去联系,这个临时Znode也将自动删除。排在它后面的那个节点,也能收到删除事件,从而获得锁。正是由于这个原因,在创建取号节点的时候,尽量创建**临时znode**节点\r\n# 3. 避免羊群效应\r\nZooKeeper的节点监听机制,能避免羊群效应 ZooKeeper这种首尾相接,后面监听前面的方式,可以避免羊群效应。所谓羊群效应就是一个节点挂掉,所有节点都去监听,然后做出反应,这样会给服务器带来巨大压力,所以有了临时顺序节点,当一个节点挂掉,只有它后面的那一个节点才做出反应。\r\n\r\nzk里有一把锁,这个锁就是zk上的一个节点。然后呢,两个客户端都要来获取这个锁。假设客户端A抢先一步,对zk发起了加分布式锁的请求,这个加锁请求是用到了zk中的一个特殊的概念,叫做“临时顺序节点”。 简单来说,就是直接在”my_lock”这个锁节点下,创建一个顺序节点,这个顺序节点有zk内部自行维护的一个节点序号。客户端A发起一个加锁请求 比如说,第一个客户端来搞一个顺序节点,zk内部会给起个名字叫做:xxx-000001。然后第二个客户端来搞一个顺序节点,zk可能会起个名字叫做:xxx-000002,最后一个数字都是依次递增的,从1开始逐次递增。zk会维护这个顺序。 所以这个时候,假如说客户端A先发起请求,就会搞出来一个顺序节点,Curator框架大概会弄成如下的样子: \r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506122316896.png)\r\n\r\n客户端A发起一个加锁请求,先会在你要加锁的node下搞一个临时顺序节点,这一大坨长长的名字都是Curator框架自己生成出来的。 然后,那个最后一个数字是”1″。因为客户端A是第一个发起请求的,所以给他搞出来的顺序节点的序号是”1″。 接着客户端A创建完一个顺序节点。还没完,他会查一下”my_lock”这个锁节点下的所有子节点,并且这些子节点是按照序号排序的,这个时候他大概会拿到这么一个集合: \r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506122316567.png)\r\n接着客户端A会走一个关键性的判断,就是说:唉!兄弟,这个集合里,我创建的那个顺序节点,是不是排在第一个啊? 如果是的话,那我就可以加锁了啊!因为明明我就是第一个来创建顺序节点的人,所以我就是第一个尝试加分布式锁的人啊! bingo!加锁成功!\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506122317986.png)\r\n客户端B过来排队 接着假如说,客户端A都加完锁了,客户端B过来想要加锁了,这个时候他会干一样的事儿:先是在”my_lock”这个锁节点下创建一个临时顺序节点,此时名字会变成类似于: \r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506122317168.png)\r\n看看下面的图: \r\n![](https://ask.qcloudimg.com/http-save/yehe-8223537/a8945c66c1a147ca15c2383ce93c0abc.png)客户端B因为是第二个来创建顺序节点的,所以zk内部会维护序号为”2″。 接着客户端B会走加锁判断逻辑,查询”my\\_lock”锁节点下的所有子节点,按序号顺序排列,此时他看到的类似于: \r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202506122318887.png)\r\n同时检查自己创建的顺序节点,是不是集合中的第一个? 明显不是啊,此时第一个是客户端A创建的那个顺序节点,序号为”01″的那个。所以加锁失败! 客户端B开启监听客户端A 加锁失败了以后,客户端B就会通过ZK的API对他的顺序节点的上一个顺序节点加一个监听器。zk天然就可以实现对某个节点的监听。\r\n![](https://ask.qcloudimg.com/http-save/yehe-8223537/7ec63614c1c40dd52c6c2726f39e6059.png) 他的上一个顺序节点,不就是下面这个吗? ![](https://ask.qcloudimg.com/http-save/yehe-8223537/63f0975abbef2bba3c1e06ba4248b0b8.png) 即客户端A创建的那个顺序节点! 所以,客户端B会对: ![](https://ask.qcloudimg.com/http-save/yehe-8223537/472fc326567bbc6cdd515c7257a43eed.png) 这个节点加一个监听器,监听这个节点是否被删除等变化!大家看下面的图。 ![](https://ask.qcloudimg.com/http-save/yehe-8223537/d0d13b38f203323ccdda5f74ccc6391e.png) 接着,客户端A加锁之后,可能处理了一些代码逻辑,然后就会释放锁。那么,释放锁是个什么过程呢? 其实很简单,就是把自己在zk里创建的那个顺序节点,也就是: ![](https://ask.qcloudimg.com/http-save/yehe-8223537/7f6501f5ce491dc7d1223d85e7cdcc05.png) 这个节点给删除。 删除了那个节点之后,zk会负责通知监听这个节点的监听器,也就是客户端B之前加的那个监听器,说:兄弟,你监听的那个节点被删除了,有人释放了锁。 ![](https://ask.qcloudimg.com/http-save/yehe-8223537/8b884ab9003d4946f30a354fd7cd033c.png) 此时客户端B的监听器感知到了上一个顺序节点被删除,也就是排在他之前的某个客户端释放了锁。 客户端B抢锁成功 此时,就会通知客户端B重新尝试去获取锁,也就是获取”my_lock”节点下的子节点集合,此时为: ![](https://ask.qcloudimg.com/http-save/yehe-8223537/8d2d7104728d1617e3d372b15159654f.png) 集合里此时只有客户端B创建的唯一的一个顺序节点了! 然后呢,客户端B判断自己居然是集合中的第一个顺序节点,bingo!可以加锁了!直接完成加锁,运行后续的业务代码即可,运行完了之后再次释放锁。 ![](https://ask.qcloudimg.com/http-save/yehe-8223537/9ebd1de7827618e8da30d24dfa333fa8.png)"},{"id":"基本概念","title":"基本概念","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"基本概念","description":"1. zookeeper数据存储 ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如 /hbase/master,其中 hbase 和 master 都是 ZNode。每个 ZNode 上都会保存自己的数据内容,同时...","relativePath":"Tech/Middleware/zookeeper/基本概念.md","rawContent":"# 1. zookeeper数据存储\r\nZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如 /hbase/master,其中 hbase 和 master 都是 ZNode。每个 ZNode 上都会保存自己的数据内容,同时会保存一系列属性信息\r\n# 2. 临时节点与持久化节点\r\n持久节点是指一旦这个 ZNode 被创建了,除非主动进行 ZNode 的移除操作,否则这个 ZNode 将一直保存在 ZooKeeper 上\r\n\r\n临时节点(Ephemeral Node)是一种特殊类型的节点,它们在创建该节点的客户端会话结束时会自动被删除。使用 ZooKeeper 的命令行客户端来检查节点的状态:\r\n```bash\r\n[zk: localhost:2181(CONNECTED) 3] stat /java-tutorial/lock-space/account_1/_c_5479718a-97e3-4320-945e-1a53b6d9afc1-lock-0000000000 \r\ncZxid = 0x13a\r\nctime = Fri Jun 13 21:16:17 CST 2025\r\nmZxid = 0x13a\r\nmtime = Fri Jun 13 21:16:17 CST 2025\r\npZxid = 0x13a\r\ncversion = 0\r\ndataVersion = 0\r\naclVersion = 0\r\nephemeralOwner = 0x1000008942b000e\r\ndataLength = 10\r\nnumChildren = 01\r\n```\r\n`stat`命令输出结果中,如果 `EphemeralOwner` 字段的值不为0则表示该节点是临时节点。`EphemeralOwner` 字段存储的是创建该临时节点的会话ID。\r\n# 3. 创建节点\r\n```bash\r\n[zk: localhost:2181(CONNECTED) 22] create /test_node_persistent\r\n[zk: localhost:2181(CONNECTED) 23] stat /test_node_persistent \r\ncZxid = 0x82a\r\nctime = Thu Jun 12 23:29:24 CST 2025\r\nmZxid = 0x82a\r\nmtime = Thu Jun 12 23:29:24 CST 2025\r\npZxid = 0x82a\r\ncversion = 0\r\ndataVersion = 0\r\naclVersion = 0\r\nephemeralOwner = 0x0\r\ndataLength = 0\r\nnumChildren = 0\r\n[zk: localhost:2181(CONNECTED) 24] create -e /test_node_ephemeral\r\nCreated /test_node_ephemeral\r\n[zk: localhost:2181(CONNECTED) 25] stat /tetst_node_ephemeral \r\ncZxid = 0x82b\r\nctime = Thu Jun 12 23:29:24 CST 2025\r\nmZxid = 0x82b\r\nmtime = Thu Jun 12 23:29:24 CST 2025\r\npZxid = 0x82b\r\ncversion = 0\r\ndataVersion = 0\r\naclVersion = 0\r\nephemeralOwner = 0x10001c233980090\r\ndataLength = 0\r\nnumChildren = 0\r\n```"},{"id":"DCN","title":"DCN","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"dcn","description":"","relativePath":"Tech/Network/DCN/DCN.md","rawContent":""},{"id":"DCN架构演进","title":"DCN架构演进","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"dcn架构演进","description":"1. 服务与网络三大耦合问题 - 交换机堆叠,存在单点风险和运维困难问题 - 物理网络大二层隧道,引入更大故障域 - 硬件墙的引入限制整体云业务规模,硬件强性能不行,无法横向扩展 2. 名词解释 NSA(Network Service Area) - NWS(Network Service): 网络...","relativePath":"Tech/Network/DCN/DCN架构演进.md","rawContent":"# 1. 服务与网络三大耦合问题\r\n- 交换机堆叠,存在单点风险和运维困难问题\r\n- 物理网络大二层隧道,引入更大故障域\r\n- 硬件墙的引入限制整体云业务规模,硬件强性能不行,无法横向扩展\r\n# 2. 名词解释\r\nNSA(Network Service Area)\r\n- NWS(Network Service): 网络服务区,BR、ELB、NAT\r\n- MGS(Manage Service): 管理服务区,负责云平台,面向租户提供的管理面服务,如APIG、DNS\r\nNC(Network Cluster): 在AZ内提供计算、存储、网络节点的接入能力,一套NC包含接入、汇聚、核心三层交换机\r\nFA: 分为FA-AZ和FA-NC\r\n- FA-AZ: 负责AZ之间互连,南北向(AZ与AZ间)的流量互连和出口\r\n- FA-NC: 负责NC之间互连,东西向(NC与NC间)\r\nAZC(AZ Connector): DCN3.0下只用于AZ之间互连,一般是两台(认为跨AZ的流量不会很大)\r\n# 3. 总结\r\n## 3.1. 交换机形态\r\nDCN2.0 TOR核心为盒式,汇聚、核心、FA均为框式\r\nDCN3.x 部分汇聚开始采用盒式\r\nDCN4.x和DCN5.0 核心开始采用框式,FA为框式\r\n> [!tip]\r\n> 规模受限于框式设备端口数,同时框式设备时延高,成本高\r\n## 3.2. FA发展\r\nDCN3.0设计中没有FA,通过AZC(AZ Connector)只负责AZ间的流量转发,AZC不承担南北向流量。南北向(公网)流量: NC -> NWS -> Transit区,然后送往骨干网或互联网。\r\n\r\nDCN3.1及以后引入了FA设备,不光承担了AZ间的流量,也承担了南北向流量,NC -> FA -> Transit(流量不过网络服务区了)\r\n## 3.3. 堆叠\r\nDCN3.x 完成了核心、汇聚、AZC/FA的去堆叠,网络服务区汇聚去堆叠,TOR堆叠\r\nDCN4.x NC内全面去交换机堆叠部署,网络服务区去堆叠部署;管理服务区汇聚去堆叠,TOR存在堆叠\r\nDCN5.x全面去堆叠,包括NC,管理服务器,网络服务区\r\n## 3.4. 路由协议选择\r\nDCN2.0协议采用OSPF,DCN3.x及以后均为BGP组网\r\nAZ内单路由域,计算存储跨平面互访无需过墙,资源访问管理区过墙\r\n## 3.5. 大二层\r\nDCN3.x开始逐步消除大二层\r\nDCN4.x消除NC内、网络服务区大二层\r\nDCN5.x才消除管理服务区大二层(受制于openstack底座能力约束)\r\n## 3.6. 管理墙/边界墙\r\nDCN3.x以下均为硬件\r\nDCN4.x硬墙、软墙结合\r\nDCN5.0,消除硬件墙,SPM/DMZ云化\r\n## 3.7. Transit区\r\nDCN3.x引入transit区,DCN4.x也存在transit区\r\nDCN5.0去transit区,BR网关下沉网络服务区(NWS)\r\n> [!note] transit区为了容灾需要选两个以上的机房,设计上想要离两个AZ都近是比较理想化的,实际很难找到。下沉NWS后,在AZ内建设POP点,这样可以保证有POP点的这些AZ能够实现最短距离,作为主力AZ,提高低时延。\r\n## 3.8. 多VRF路由平面隔离\r\nDCN2.0存在多VRF路由平面隔离,从DCN3.x开始,就不存在多VRF路由平面隔离。\r\n> [!tip]\r\n> 多VRF路由平面隔离会极大地增加DCN管理复杂度\r\n"},{"id":"交换机堆叠","title":"交换机堆叠","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"交换机堆叠","description":"1. 背景 主机连接单个交换机,存在单点故障问题(单个交换机故障),解决tor交换机单点故障的思路有两个。 - 上层服务(计算、网络)保证高可用,当一个tor下主机故障时,可以快速在另一台tor下拉起新主机 - 两台tor交换机堆叠,保证tor交换机的高可靠 2. 概念 交换机堆叠一般是指被背板堆叠...","relativePath":"Tech/Network/DCN/交换机堆叠.md","rawContent":"# 1. 背景\r\n主机连接单个交换机,存在单点故障问题(单个交换机故障),解决tor交换机单点故障的思路有两个。\r\n- 上层服务(计算、网络)保证高可用,当一个tor下主机故障时,可以快速在另一台tor下拉起新主机\r\n- 两台tor交换机堆叠,保证tor交换机的高可靠\r\n# 2. 概念\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250220225558639.png)\r\n交换机堆叠一般是指被**背板堆叠**,通过交换机主板总线引出的一个特殊端口,进行交换机与交换机之间的总线级数据交换,其**带宽取决于交换机的总线带宽**。大概是因为这个特殊的堆叠端口,往往放置在交换机背面,区别于其他网口,所以才叫做背板堆叠。此外,堆叠的线路像一条并口线,很宽,很短(不超过20厘米),所以交换机必须上下紧挨着叠放在机柜中,才能插上这种线,这大概也是堆叠这个词的由来。该功能需要交换机支持,一般都要同品牌甚至同型号的交换机才可以,毕竟各家交换机往往都有自己独特的数据处理模式,所以不是任何两台交换机都能堆叠在一起的。\r\n\r\n堆叠不仅可以堆叠两台交换机,还能一路路串联,堆叠更多交换机。堆叠扩展了交换机的端口范围,比如两台48口的交换机,经过堆叠后,你可以把这一组交换机当做96口的整机来使用。堆叠后的交换机之间的流量都要经过堆叠线路,带宽瓶颈受限于**单台主机的总线带宽**,多个交换机堆叠也无法扩展带宽。\r\n# 3. 存在的问题\r\n堆叠交换机的数据面可以是多个,但是控制面只有一个,控制面出现问题,数据面转发会有异常。因此只能解决数据面单点问题,无法解决控制面单点问题。同时控制面只有一个工作,也存在资源浪费问题。\r\n\r\n主控板故障或堆叠链路中断时,可能触发分裂脑(Split-Brain)问题,导致网络分区。\r\n\r\n【升级困难】交换机操作系统升级冷不丁,需要先堆叠分裂、升级、再重新组堆叠,业务流量会发生多次切换(切换动作非无损) 。"},{"id":"大二层架构的问题","title":"大二层架构的问题","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"大二层架构的问题","description":"1. 历史背景 网关在汇聚上的原因是因为openstack平面约束,要求资源池里面的节点必须在一个二层上。 管理服务区解大二层原因是管理区容灾1.0方案依赖vip在az间漂移,管理区汇聚下就是一个二层。同时,az之间还需要在两个az的汇聚交换机上通过硬件vxlan打一个二层隧道。 2. 大二层的问题...","relativePath":"Tech/Network/DCN/大二层架构的问题.md","rawContent":"# 1. 历史背景\r\n网关在汇聚上的原因是因为openstack平面约束,要求资源池里面的节点必须在一个二层上。\r\n\r\n管理服务区解大二层原因是管理区容灾1.0方案依赖vip在az间漂移,管理区汇聚下就是一个二层。同时,az之间还需要在两个az的汇聚交换机上通过硬件vxlan打一个二层隧道。\r\n\r\n# 2. 大二层的问题\r\n广播域过大,arp欺骗、广播泛洪、环路等会影响整个广播域。一个广播域其实就是一个故障域。网关在汇聚上,故障会影响和扩散到汇聚下的所有tor下的所有主机。把网关下沉到tor上,缩小广播域,可以降低故障域。\r\n\r\n# 3. 解大二层\r\n管理面,容灾1.1后不再依赖vip\r\n数据面,openstack架构优化,资源池跨三层"},{"id":"(七)Linux下实现NAT","title":"(七)Linux下实现NAT","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"七linux下实现nat","description":"在NAT Overview一文中,我们介绍了在linux下通过iptables实现NAT功能的命令及原理。本文以容器为例,进行几个简单的实验来进一步了解linux下NAT的实现过程和应用。 1. nat在容器中的应用 1.1. SNAT linux下一个最典型的NAT应用就是docker容器借助宿主...","relativePath":"Tech/Network/NAT/(七)Linux下实现NAT.md","rawContent":"在[[NAT Overview]]一文中,我们介绍了在linux下通过iptables实现NAT功能的命令及原理。本文以容器为例,进行几个简单的实验来进一步了解linux下NAT的实现过程和应用。\r\n# 1. nat在容器中的应用\r\n## 1.1. SNAT\r\nlinux下一个最典型的NAT应用就是docker容器借助宿主机网络访问外网,执行`iptables -t nat -nvL`可以看到如下输出:\r\n```text\r\nChain POSTROUTING (policy ACCEPT 33 packets, 2238 bytes)\r\n pkts bytes target prot opt in out source destination\r\n 3 211 \r\n\tall -- * !docker0 172.17.0.0/16 0.0.0.0/0\r\n```\r\n\r\n> 任意网口进入,访问非docker0网桥的流量,并且源地址为172.17.0.0/16网段的,把它交给MASQUERADE处理。MASQUERADE作用是从当前出接口上自动获取当前ip地址来做NAT,这样就实现了容器实现宿主机网卡访问外部网络的目的。\r\n### 1.1.1. 抓包结果\r\n在容器中执行`ping 8.8.8.8`,在`docker0`网桥抓包如下:\r\n```shell\r\n[root@vultr ~]# tcpdump icmp -i docker0 -c 4 -nne\r\ntcpdump: verbose output suppressed, use -v or -vv for full protocol decode\r\nlistening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes\r\n14:51:54.443718 02:42:ac:11:00:02 > 02:42:d5:35:3a:3c, ethertype IPv4 (0x0800), length 98: 172.17.0.2 > 8.8.8.8: ICMP echo request, id 28, seq 265, length 64\r\n14:51:54.445275 02:42:d5:35:3a:3c > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 98: 8.8.8.8 > 172.17.0.2: ICMP echo reply, id 28, seq 265, length 64\r\n14:51:55.445564 02:42:ac:11:00:02 > 02:42:d5:35:3a:3c, ethertype IPv4 (0x0800), length 98: 172.17.0.2 > 8.8.8.8: ICMP echo request, id 28, seq 266, length 64\r\n14:51:55.446986 02:42:d5:35:3a:3c > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 98: 8.8.8.8 > 172.17.0.2: ICMP echo reply, id 28, seq 266, length 64\r\n```\r\n主机eth0口抓包:\r\n```shell\r\n[root@vultr ~]# tcpdump icmp -i eth0 -c 4 -nne\r\ntcpdump: verbose output suppressed, use -v or -vv for full protocol decode\r\nlistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes\r\n14:52:36.512507 56:00:04:7c:7c:23 > 06:7c:16:88:fe:87, ethertype IPv4 (0x0800), length 98: 45.77.200.180 > 8.8.8.8: ICMP echo request, id 28, seq 307, length 64\r\n14:52:36.513878 06:7c:16:88:fe:87 > 56:00:04:7c:7c:23, ethertype IPv4 (0x0800), length 98: 8.8.8.8 > 45.77.200.180: ICMP echo reply, id 28, seq 307, length 64\r\n14:52:37.514124 56:00:04:7c:7c:23 > 06:7c:16:88:fe:87, ethertype IPv4 (0x0800), length 98: 45.77.200.180 > 8.8.8.8: ICMP echo request, id 28, seq 308, length 64\r\n14:52:37.515451 06:7c:16:88:fe:87 > 56:00:04:7c:7c:23, ethertype IPv4 (0x0800), length 98: 8.8.8.8 > 45.77.200.180: ICMP echo reply, id 28, seq 308, length 64\r\n```\r\n## 1.2. dnat\r\n容器支持通过`-p`参数将宿主机端口映射到容器端口,原理就是通过iptables实现的dnat。\r\n```shell\r\n[root@vultr ~]# docker run -it -p 10000:8000 centos/python-38-centos7 /bin/bash\r\n(app-root) python -m http.server\r\nServing HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...\r\n```\r\n执行`iptables -t nat -nvL`可以看到如下规则,其中`ADDRTYPE match dst-type LOCAL`表达的意思是将目的地址类型是主机本地网络的数据包jump到一个名叫docker的链。从非`docker0`网桥上进入的流量,目的端口为10000的转发到容器`172.17.0.2`的8000端口。\r\n```shell\r\nChain PREROUTING (policy ACCEPT 64 packets, 2997 bytes)\r\n pkts bytes target prot opt in out source destination\r\n31428 1534K DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL\r\nChain DOCKER (2 references)\r\n pkts bytes target prot opt in out source destination\r\n 0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0\r\n 0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:10000 to:172.17.0.2:8000\r\n```\r\n### 1.2.1. 抓包\r\n`eth0`口抓包\r\n```shell\r\n[root@vultr ~]# tcpdump tcp -i eth0 port 10000 -nne\r\ntcpdump: verbose output suppressed, use -v or -vv for full protocol decode\r\nlistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes\r\n15:22:06.799116 06:7c:16:88:fe:87 > 56:00:04:7c:7c:23, ethertype IPv4 (0x0800), length 78: 114.246.99.100.60677 > 45.77.200.180.10000: Flags [S], seq 2189230313, win 65535, options [mss 1440,nop,wscale 6,nop,nop,TS val 3976681198 ecr 0,sackOK,eol], length 0\r\n15:22:06.800136 56:00:04:7c:7c:23 > 06:7c:16:88:fe:87, ethertype IPv4 (0x0800), length 74: 45.77.200.180.10000 > 114.246.99.100.60677: Flags [S.], seq 3797373137, ack 2189230314, win 28960, options [mss 1460,sackOK,TS val 2686973908 ecr 3976681198,nop,wscale 10], length 0\r\n15:22:07.066851 06:7c:16:88:fe:87 > 56:00:04:7c:7c:23, ethertype IPv4 (0x0800), length 66: 114.246.99.100.60677 > 45.77.200.180.10000: Flags [.], ack 1, win 2052, options [nop,nop,TS val 3976681467 ecr 2686973908], length 0\r\n15:22:07.067613 06:7c:16:88:fe:87 > 56:00:04:7c:7c:23, ethertype IPv4 (0x0800), length 149: 114.246.99.100.60677 > 45.77.200.180.10000: Flags [P.], seq 1:84, ack 1, win 2052, options [nop,nop,TS val 3976681467 ecr 2686973908], length 83\r\n15:22:07.067658 56:00:04:7c:7c:23 > 06:7c:16:88:fe:87, ethertype IPv4 (0x0800), length 66: 45.77.200.180.10000 > 114.246.99.100.60677: Flags [.], ack 84, win 29, options [nop,nop,TS val 2686974176 ecr 3976681467], length 0\r\n15:22:07.075909 56:00:04:7c:7c:23 > 06:7c:16:88:fe:87, ethertype IPv4 (0x0800), length 220: 45.77.200.180.10000 > 114.246.99.100.60677: Flags [P.], seq 1:155, ack 84, win 29, options [nop,nop,TS val 2686974184 ecr 3976681467], length 154\r\n15:22:07.076074 56:00:04:7c:7c:23 > 06:7c:16:88:fe:87, ethertype IPv4 (0x0800), length 431: 45.77.200.180.10000 > 114.246.99.100.60677: Flags [FP.], seq 155:520, ack 84, win 29, options [nop,nop,TS val 2686974184 ecr 3976681467], length 365\r\n15:22:07.373595 06:7c:16:88:fe:87 > 56:00:04:7c:7c:23, ethertype IPv4 (0x0800), length 66: 114.246.99.100.60677 > 45.77.200.180.10000: Flags [.], ack 155, win 2050, options [nop,nop,TS val 3976681774 ecr 2686974184], length 0\r\n15:22:07.373655 06:7c:16:88:fe:87 > 56:00:04:7c:7c:23, ethertype IPv4 (0x0800), length 66: 114.246.99.100.60677 > 45.77.200.180.10000: Flags [.], ack 521, win 2044, options [nop,nop,TS val 3976681774 ecr 2686974184], length 0\r\n15:22:07.374062 06:7c:16:88:fe:87 > 56:00:04:7c:7c:23, ethertype IPv4 (0x0800), length 66: 114.246.99.100.60677 > 45.77.200.180.10000: Flags [F.], seq 84, ack 521, win 2048, options [nop,nop,TS val 3976681774 ecr 2686974184], length 0\r\n15:22:07.374124 56:00:04:7c:7c:23 > 06:7c:16:88:fe:87, ethertype IPv4 (0x0800), length 66: 45.77.200.180.10000 > 114.246.99.100.60677: Flags [.], ack 85, win 29, options [nop,nop,TS val 2686974482 ecr 3976681774], length 0\r\n```\r\n`docker0`网桥抓包\r\n```shell\r\n[root@vultr ~]# tcpdump tcp -i docker0 port 8000 -nne\r\ntcpdump: verbose output suppressed, use -v or -vv for full protocol decode\r\nlistening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes\r\n15:22:06.799241 02:42:d5:35:3a:3c > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 78: 114.246.99.100.60677 > 172.17.0.2.8000: Flags [S], seq 2189230313, win 65535, options [mss 1440,nop,wscale 6,nop,nop,TS val 3976681198 ecr 0,sackOK,eol], length 0\r\n15:22:06.800061 02:42:ac:11:00:02 > 02:42:d5:35:3a:3c, ethertype IPv4 (0x0800), length 74: 172.17.0.2.8000 > 114.246.99.100.60677: Flags [S.], seq 3797373137, ack 2189230314, win 28960, options [mss 1460,sackOK,TS val 2686973908 ecr 3976681198,nop,wscale 10], length 0\r\n15:22:07.066898 02:42:d5:35:3a:3c > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 66: 114.246.99.100.60677 > 172.17.0.2.8000: Flags [.], ack 1, win 2052, options [nop,nop,TS val 3976681467 ecr 2686973908], length 0\r\n15:22:07.067625 02:42:d5:35:3a:3c > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 149: 114.246.99.100.60677 > 172.17.0.2.8000: Flags [P.], seq 1:84, ack 1, win 2052, options [nop,nop,TS val 3976681467 ecr 2686973908], length 83\r\n15:22:07.067645 02:42:ac:11:00:02 > 02:42:d5:35:3a:3c, ethertype IPv4 (0x0800), length 66: 172.17.0.2.8000 > 114.246.99.100.60677: Flags [.], ack 84, win 29, options [nop,nop,TS val 2686974176 ecr 3976681467], length 0\r\n15:22:07.075862 02:42:ac:11:00:02 > 02:42:d5:35:3a:3c, ethertype IPv4 (0x0800), length 220: 172.17.0.2.8000 > 114.246.99.100.60677: Flags [P.], seq 1:155, ack 84, win 29, options [nop,nop,TS val 2686974184 ecr 3976681467], length 154\r\n15:22:07.076061 02:42:ac:11:00:02 > 02:42:d5:35:3a:3c, ethertype IPv4 (0x0800), length 431: 172.17.0.2.8000 > 114.246.99.100.60677: Flags [FP.], seq 155:520, ack 84, win 29, options [nop,nop,TS val 2686974184 ecr 3976681467], length 365\r\n15:22:07.373634 02:42:d5:35:3a:3c > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 66: 114.246.99.100.60677 > 172.17.0.2.8000: Flags [.], ack 155, win 2050, options [nop,nop,TS val 3976681774 ecr 2686974184], length 0\r\n15:22:07.373659 02:42:d5:35:3a:3c > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 66: 114.246.99.100.60677 > 172.17.0.2.8000: Flags [.], ack 521, win 2044, options [nop,nop,TS val 3976681774 ecr 2686974184], length 0\r\n15:22:07.374070 02:42:d5:35:3a:3c > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 66: 114.246.99.100.60677 > 172.17.0.2.8000: Flags [F.], seq 84, ack 521, win 2048, options [nop,nop,TS val 3976681774 ecr 2686974184], length 0\r\n15:22:07.374090 02:42:ac:11:00:02 > 02:42:d5:35:3a:3c, ethertype IPv4 (0x0800), length 66: 172.17.0.2.8000 > 114.246.99.100.60677: Flags [.], ack 85, win 29, options [nop,nop,TS val 2686974482 ecr 3976681774], length 0\r\n```"},{"id":"(九)高性能NAT","title":"(九)高性能NAT","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"九高性能nat","description":"","relativePath":"Tech/Network/NAT/(九)高性能NAT.md","rawContent":""},{"id":"(五)NAT ALG","title":"(五)NAT ALG","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":8,"slug":"五nat-alg","description":"--- title: NAT ALG date: 2023-02-01 categories: - network tags: - network - nat --- 能够对特定的应用层协议进行转换,在对这些特定的应用层协议进行转换过程中,通过的会话信息来改变封装在报文载荷中的和端口信息,最终实现下...","relativePath":"Tech/Network/NAT/(五)NAT ALG.md","rawContent":" ---\r\ntitle: NAT ALG\r\ndate: 2023-02-01\r\ncategories:\r\n- network\r\ntags:\r\n- network\r\n- nat\r\n---\r\n\r\n`NAT ALG`能够对特定的应用层协议进行转换,在对这些特定的应用层协议进行`NAT`转换过程中,通过`NAT`的会话信息来改变封装在`IP`报文载荷中的`IP`和端口信息,最终实现`NAT`下应用层协议的正常通信。在第四章中我们介绍的ICMP差错报文也可以算是NAT ALG的一种应用,本文将以`DNS`和`FTP`协议为例,详细阐述一下`NAT ALG`的工作原理。\r\n\r\n# 1. DNS\r\n[[应用层DNS协议解析|DNS]]是一个应用层协议,可以根据域名获取对应的`IP`地址。这个`IP`地址是放在DNS响应报文的`Answers`字段中,而整个DNS协议报文都位于四层协议头的`payload`中,普通的NAT并不会识别并替换这个地址。\r\n![|375](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424155506.png)\r\n\r\n`NAT ALG`服务于DNS主要有以下两种场景:\r\n1. 私网的客户端访问公网上的DNS服务器进行A查询,根据域名获得私网服务器的私网IP地址。由于公网的DNS服务器解析的是公网地址,因此需要通过`NAT ALG`将DNS响应报文中的`Answers`字段中的公网地址替换为内网主机可以访问的私网IP。\r\n2. 公网的客户端访问私网上的DNS服务器进行A查询,根据域名获得内网服务器的公网IP地址。由于私网的DNS服务器解析的是私网地址,因此需要通过`NAT ALG`将DNS响应报文中的`Answers`字段中的私网地址替换为公网主机可以访问的公网IP。\r\n# 2. FTP\r\nFTP有两种不同工作模式:PORT(主动模式)与PASV(被动模式)。两种模式都需要用到两个连接:控制连接与数据连接,控制连接专门用于FTP控制命令及命令执行信息传输,数据连接专门用于传输数据。\r\n## 2.1. 主动模式\r\n未经过NAT设备的通信过程如下:\r\n1. 客户端用端口N与FTP服务器的21端口建立一个控制通道,发送一条命令告诉FTP服务端(即通常说的PORT命令),我的数据通道的通信地址是IP1,数据通道的端口N+1,服务端收到请求后回复ACK确认。\r\n2. 服务端确认后,用源端口20主动与客户端IP1:N+1建立连接,进行数据通信。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307052323754.png)\r\n\r\n主动模式下,客户端的ip和端口放在了载荷中,因此这里我们假设客户端在NAT设备环境下(内网),服务端在外网,经过支持NAT ALG设备后的通信过程如下:\r\n1. 客户端用端口internalPort与FTP服务器的21端口建立一个控制通道,发送一条命令告诉FTP服务端(即通常说的PORT命令),我的数据通道的通信地址是internalPort,数据通道的端口internalPort+1。\r\n2. 报文到达NAT设备,NAT设备识别是FTP协议后,将IP报文的源IP替换为transitIp,源端口替换为transitPort_N,将载荷部分的数据通道通信地址IP1也替换为transitIp,数据通道的端口替换为transitPort_M。此时NAT设备需要生成两个会话,分别用于控制信道和数据信道。\r\n3. 服务端确认后,用源端口20主动与客户端transitIp:transitPort_M建立连接,进行数据通信。\r\n4. 报文到达NAT设备,NAT设备根据数据信道会话将目的ip和端口替换为internalIp和internalPort+1,发送到内网。\r\n## 2.2. 被动模式\r\n未经过NAT设备的通信过程如下:\r\n1. 客户端使用ip1和端口N与FTP服务端(ip2,端口21)建立一个控制通道,发送一条命令告诉服务端(即通常说的PASV命令),我将使用被动模式与你通信。服务端收请求后,会告知客户端我的IP是ip2和监听端口M,你可以和我的这个IP和端口通信。\r\n2. 客户端收到信息后,使用源端口N+1,与服务端ip2:M建立连接,进行数据通信。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307052345354.png)\r\n\r\n\r\n被动模式下服务端的ip和端口被放在了报文载荷中,因此这里我们假设服务端在NAT设备环境下(内网),客户端在外网,经过支持NAT ALG设备后的通信过程如下:\r\n1. 客户端使用externalIp和端口externalPort与FTP服务端(ip为transitIp,端口transitPort_N)建立一个控制通道,发送一条命令告诉服务端(即通常说的PASV命令),我将使用被动模式与你通信。\r\n2. 【配置DNAT】报文到达NAT设备后,将目的ip替换为internalIp,目的端口替换为internalPort(通常为21)。\r\n3. 服务端收请求后,会告知客户端我的IP是internalIp和监听端口internalPort,你可以和我的这个IP和端口通信。\r\n4. 【配置SNAT】报文到达NAT设备后,NAT设备识别是FTP协议后,将源IP替换为transitIp,源端口替换为transitPort_N,同时将载荷部署的数据通道通信地址替换为transitIp,端口替换为transitPort_M,并生成会话。\r\n5. 客户端收到信息后,使用源端口externalPort+1,与服务端transitIp:transitPort_M建立连接,进行数据通信。\r\n6. 报文到达NAT设备后,匹配已生成会话,将数据报文目的IP替换为internalIp,目的端口替换为internalPort。\r\n# 3. 多通道协议与动态会话\r\n传统的NAT会话都是由内网主机发起的某条流触发生成的,会话也只对这条流生效,根据ip报文的五元组进行匹配和转换。为了适应某些多通道协议,NAT ALG除了根据ip头的五元组生成会话外,还需要根据ip报文载荷中可能包含的ip和端口信息生成额外的会话以支撑其他通道的正常流量转发。\r\n\r\n# 4. 参考链接\r\n1. [防火墙ALG技术之DNS协议穿墙术](https://www.ctfiot.com/6506.html)\r\n2. [NAT ALG原理与应用-新华三集团-H3C](http://www.h3c.com/cn/d_201206/922132_30005_0.htm)\r\n\r\n"},{"id":"(八)高可靠NAT","title":"(八)高可靠NAT","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"八高可靠nat","description":"1. 双机冷备 2. 双机热备 双机热备可以极大地提升NAT服务的可靠性和承载能力,但是两台NAT设备上的NAT配置也需要完全相同。这样就会出现一个问题:如果两个NAT设备分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了优先级的概念。 在双机热备的环境...","relativePath":"Tech/Network/NAT/(八)高可靠NAT.md","rawContent":"\r\n# 1. 双机冷备\r\n# 2. 双机热备\r\n双机热备可以极大地提升NAT服务的可靠性和承载能力,但是两台NAT设备上的NAT配置也需要完全相同。这样就会出现一个问题:如果两个NAT设备分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了优先级的概念。\r\n\r\n在双机热备的环境中,如果某个设备被配置为高优先级,在进行端口映射的时候端口取值范围为1024~35000;如果地址池被配置为低优先级,其端口取值范围为35001~65535。这样主备两台防火墙虽然使用相同的NAT地址池中的地址,但是由于地址池的优先级不同,所以就不会出现NAT转换后公网IP和公网端口完全相同的情况了。\r\n\r\n正常情况下两个主机各自分配不同的端口段,一旦某个NAT主机故障后,将由另一台NAT主机承载全部的流量。此时面临两个问题:\r\n1. 单机的承载瓶颈,这个问题比较无解,妥协方案是通过控制单机设备平时负载不高于50%。两层架构下,分配会话的主机和转发主机配比较高,负责分配会话的主机数量相对较少,这种情况下浪费部分性能也是可以接受的。\r\n2. 在内网主机访问qps比较大的情况下,有可能出现端口不够用的情况,此时需要有管理手段可以通知到健康节点抢占故障节点的端口来分配。同时,为了保证端口分配和已有会话不冲突以及便于在故障节点恢复后能够将抢占端口段还回去,两个设备之间需要保持会话同步。"},{"id":"(六)路由器配置NAT","title":"(六)路由器配置NAT","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"六路由器配置nat","description":"本章节主要以cisco路由器配置为例,使用GNS3仿真平台搭建一个最简单的实验环境进行验证。 1. 常用命令(cisco) 首先介绍本实验可能用到的一些命令,先有个概念,后面再在实验中熟悉并掌握。 1.1. VPC 1.2. 路由器 2. 环境 2.1. 配置PC 2.1.1. PC1 2.1.2....","relativePath":"Tech/Network/NAT/(六)路由器配置NAT.md","rawContent":"本章节主要以cisco路由器配置为例,使用GNS3仿真平台搭建一个最简单的实验环境进行验证。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307110023052.png)\r\n\r\n# 1. 常用命令(cisco)\r\n首先介绍本实验可能用到的一些命令,先有个概念,后面再在实验中熟悉并掌握。\r\n## 1.1. VPC\r\n```shell\r\n# 配置ip、掩码和网关\r\nip address/mask gateway\r\n# show ip\r\n查看ip\r\n```\r\n## 1.2. 路由器\r\n```shell\r\n# 进入全局模式\r\nconfigure terminal\r\n# 查看所有接口\r\ndo show interface\r\n# 进入接口模式\r\ninterface g0/0\r\n# 配置路由器接口ip和掩码\r\nip address 192.168.0.1 255.255.255.0\r\n# 激活\r\nno shut\r\n# 退出\r\nexit\r\n# 查看路由\r\nshow ip route\r\n# 指定NAT内部接口 在内网相应接口的接口配置模式下执行\r\nip nat inside \r\n# 指定NAT外部接口 在外网相应接口的接口配置模式下执行\r\nip nat outside\r\n# 在内部地址与外部地址之间建立静态地址转换关系\r\nip nat inside source static [NAT前的内部地址] [NAT后的外部地址] \r\n# 在外部地址与内部地址之间建立静态地址转换关系\r\nip nat outside source static [NAT前的外部地址] [NAT后的内部地址] \r\n# 显示当前存在的NAT转换信息\r\nshow ip nat translations\r\n# 查看NAT的统计信息\r\nshow ip nat statics\r\n# 显示当前存在的NAT转换的详细信息\r\nshow ip nat translations verbose\r\n# 跟踪NAT操作,显示出每个被转换的数据包\r\ndebug ip nat\r\n# 删除静态NAT映射\r\nno ip nat inside source static local-address global-address \r\nno ip nat outside source static local-address global-address \r\n# 删除动态NAT映射表中的所有内容\r\nclear ip nat translations *\r\n# pnat\r\nip nat inside source static {UDP|TCP} local-address port global-address port\r\nip nat outside source static {UDP|TCP} local-address port global-address port\r\n# 定义一个标准的access-list规则,声明允许哪些内部本地地址可以进行动态地址转换,其中,access-list-number为1-99之间的一个任意整数,通配符为子网掩码的反掩码\r\naccess-list access-list-number permit 源地址 通配符 \r\n# 定义内部全局地址池\r\nip nat pool 地址池名 起始IP地址 终止IP地址 netmask 子网掩码\r\n# 定义符合先前定义的access-list规则的IP数据包与先前定义的地址池中的IP地址进行转换,只分配ip,不复用端口\r\nip nat inside source list access-list-number pool 内部全局地址池名\r\n# 定义符合先前定义的access-list规则的IP数据包与先前定义的地址池中的IP地址进行复用地址转换,同ip复用端口\r\nip nat inside source list list-number pool 内部全局地址池名 overload\r\n# 删除全局地址池 \r\nno ip nat pool name \r\n# 删除访问列表\r\nno access-list access-list-number \r\n# 删除动态源地址转换\r\nno ip nat inside source list list-number pool 内部全局地址池名\r\nno ip nat inside source list list-number pool 内部全局地址池名 overload\r\n```\r\n# 2. 环境\r\n## 2.1. 配置PC\r\n### 2.1.1. PC1\r\n```shell\r\nPC1> ip 192.168.0.2/24 192.168.0.1\r\nChecking for duplicate address...\r\nPC1 : 192.168.0.2 255.255.255.0 gateway 192.168.0.1\r\n```\r\n### 2.1.2. PC2\r\n```shell\r\nPC2> ip 30.0.0.2/24 30.0.0.1\r\nChecking for duplicate address...\r\nPC2 : 10.0.0.2 255.255.255.0 gateway 10.0.0.1\r\n```\r\n## 2.2. 配置路由器\r\n### 2.2.1. R1\r\n#### 2.2.1.1. g0/0\r\n```shell\r\nR1(config)#interface g0/0\r\nR1(config-if)#ip address 192.168.0.1 255.255.255.0\r\nR1(config-if)#no shut\r\nR1(config-if)#\r\n*Jul 10 14:48:52.979: %LINK-3-UPDOWN: Interface GigabitEthernet0/0, changed state to up\r\n*Jul 10 14:48:53.979: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/0, changed state to up\r\n```\r\n#### 2.2.1.2. g1/0\r\n```shell\r\nR1(config)#interface g1/0\r\nR1(config-if)#ip address 20.0.0.2 255.255.255.0\r\nR1(config-if)#no shut\r\n```\r\n### 2.2.2. R2\r\n#### 2.2.2.1. #### g0/0\r\n```shell\r\nR1(config)#interface g0/0\r\nR1(config-if)#ip address 20.0.0.3 255.255.255.0\r\nR1(config-if)#no shut\r\n```\r\n#### 2.2.2.2. g1/0\r\n```shell\r\nR1(config)#interface g1/0\r\nR1(config-if)#ip address 30.0.0.1 255.255.255.0\r\nR1(config-if)#no shut\r\n```\r\n\r\n### 2.2.3. 查看路由\r\n#### 2.2.3.1. R1\r\n```shell\r\nR1(config-if)#do show ip route\r\nCodes: C - connected, S - static, R - RIP, M - mobile, B - BGP\r\n       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area \r\n       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2\r\n       E1 - OSPF external type 1, E2 - OSPF external type 2\r\n       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2\r\n       ia - IS-IS inter area, * - candidate default, U - per-user static route\r\n       o - ODR, P - periodic downloaded static route\r\n\r\nGateway of last resort is not set\r\n\r\n     20.0.0.0/24 is subnetted, 1 subnets\r\nC     20.0.0.0 is directly connected, GigabitEthernet1/0\r\nC    192.168.0.0/24 is directly connected, GigabitEthernet0/0\r\n```\r\n存在两条直连路由\r\n#### 2.2.3.2. R2\r\n```shell\r\nR2(config)#do show ip route\r\nCodes: C - connected, S - static, R - RIP, M - mobile, B - BGP\r\n       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area \r\n       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2\r\n       E1 - OSPF external type 1, E2 - OSPF external type 2\r\n       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2\r\n       ia - IS-IS inter area, * - candidate default, U - per-user static route\r\n       o - ODR, P - periodic downloaded static route\r\n\r\nGateway of last resort is not set\r\n\r\n     20.0.0.0/24 is subnetted, 1 subnets\r\nC       20.0.0.0 is directly connected, GigabitEthernet0/0\r\n     30.0.0.0/24 is subnetted, 1 subnets\r\nC       30.0.0.0 is directly connected, GigabitEthernet1/0\r\n```\r\n存在两条直连路由\r\n\r\n### 2.2.4. ping实验\r\n此时两台路由器上并没有运行任何路由协议,都只存在直连路由,因此在pc1上很显然是ping不通pc2的,因为R1并不知道`30.0.0.0/24`网段下一跳应该送到何处。\r\n```shell\r\n[PC1> ping 30.0.0.2/24 \r\n*192.168.0.1 icmp_seq=1 ttl=255 time=45.867 ms (ICMP type:3, code:1, Destination host unreachable)\r\n\t*192.168.0.1 icmp_seq=2 ttl=255 time=11.573 ms (ICMP type:3, code:1, Destination host unreachable)\r\n*192.168.0.1 icmp_seq=3 ttl=255 time=8.511 ms (ICMP type:3, code:1, Destination host unreachable)]( ping 30.0.0.2\r\n\r\n*192.168.0.1 icmp_seq=1 ttl=255 time=11.198 ms (ICMP type:3, code:1, Destination host unreachable)\r\n*192.168.0.1 icmp_seq=2 ttl=255 time=12.263 ms (ICMP type:3, code:1, Destination host unreachable)>)\r\n```\r\n## 2.3. 配置静态NAT\r\n### 2.3.1. inside方式\r\n```shell\r\nR1(config)#ip nat inside source static 192.168.0.2 20.0.0.4\r\nR1(config)#interface g0/0\r\nR1(config-if)#ip nat inside\r\n\r\n*Jul 6 16:20:25.563: %LINEPROTO-5-UPDOWN: Line protocol on Interface NVI0, changed state to up\r\nR1(config)#interface g1/0\r\nR1(config-if)#ip nat outside\r\nR1(config)#do show ip nat translations\r\nPro Inside global Inside local Outside local Outside global\r\n--- 20.0.0.4 192.168.0.2 --- ---\r\n\r\nR1(config)#ip route 30.0.0.0 255.255.255.0 20.0.0.3\r\n```\r\n\r\n这里我们在R1上添加了30.0.0.0/24的路由,下一跳送给R2,但是R2上并没有添加指向192.168.0.0/24的路由。如果不对源地址进行NAT,R2路由器是不知道192.168.0.0/24网段下一跳应该送到哪里的,但是经过NAT后源地址已经变成了20.0.0.4,R2上有这条直连路由,流量经过NAT后可以通。\r\n\r\n#### 2.3.1.1. pc1 ping 30.0.0.2\r\npc1抓包,NAT前\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307102331836.png)\r\npc2抓包,NAT后\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307102332512.png)\r\n去程流量从inside口入,匹配nat规则,源地址替换为20.0.0.4;\r\n回程流量匹配会话,目的地址由20.0.0.4替换为192.168.0.2\r\n\r\n#### 2.3.1.2. pc2 ping 192.168.0.2\r\n配置路由\r\n```shell\r\nR2(config)#ip route 192.168.0.0 255.255.255.0 20.0.0.2\r\n```\r\n抓包如下:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307110013686.png)\r\n去程从outside口入,不进行NAT地址替换,pc1看到的源地址就是30.0.0.2;\r\n回程从inside口入,进行NAT地址替换,源地址替换为20.0.0.4;\r\npc2访问的目的是192.168.0.2,reply报文源地址是20.0.0.4,地址不匹配,流量不通。\r\n#### 2.3.1.3. pc2 ping 20.0.0.4\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307110016948.png)\r\n去程从outside口入,不进行NAT地址替换,30.0.0.2 -> 20.0.0.4;\r\n回程从inside口入,进行NAT地址替换,192.168.0.2 -> 30.0.0.2变为20.0.0.4 -> 30.0.0.2;\r\n流量正常。\r\n\r\n### 2.3.2. outside方式\r\n```shell\r\nR1(config)#ip nat outside source static 30.0.0.2 20.0.0.4\r\nR1(config)#interface g0/0\r\nR1(config-if)#ip nat inside\r\n\r\n*Jul 6 16:20:25.563: %LINEPROTO-5-UPDOWN: Line protocol on Interface NVI0, changed state to up\r\nR1(config)#interface g1/0\r\nR1(config-if)#ip nat outside\r\nR1(config)#do show ip nat translations\r\nPro Inside global Inside local Outside local Outside global\r\n--- --- --- 20.0.0.4 30.0.0.2\r\n\r\nR1(config)#ip route 30.0.0.0 255.255.255.0 20.0.0.3\r\n\r\nR2(config)#ip route 192.168.0.0 255.255.255.0 20.0.0.2\r\n```\r\n\r\n#### 2.3.2.1. p2 ping 192.168.0.2\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307102344888.png)\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307102344140.png)\r\n去程从R1的outside口进,匹配nat规则,源地址替换为20.0.0.4;\r\n返程根据会话将目的地址20.0.0.4替换为30.0.0.2;\r\n流量正常\r\n\r\n#### 2.3.2.2. p1 ping 30.0.0.2\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307102350377.png)\r\n由于是在outside配置的nat规则,因此去程从inside口进,并不会进行nat,pc2收到的源地址就是192.168.0.2;\r\n返程从outside口进时匹配到了nat规则,将源地址30.0.0.2替换为20.0.0.4;\r\n对于pc1来说,看到的源地址20.0.0.4和发出的报文目的地址30.0.0.2不匹配,流量不通。\r\n\r\n#### 2.3.2.3. pc1 ping 20.0.0.4\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307110001925.png)\r\n由于是在outside配置的nat规则,因此去程并不会进行nat,pc2收到的源地址就是192.168.0.2,而返程在outside口进时匹配到了nat规则,将源地址30.0.0.2替换为20.0.0.4,对于pc1来说就是看到的与20.0.0.4这个地址在通信。\r\n\r\n### 2.3.3. 总结\r\n1. inside规则匹配从inside口流入的流量并替换**源地址**;outside规则匹配从outside口流入的流量并替换**源地址**。两个规则是相对的,inside配置的规则,对于内网主机来说看到的外网地址是真实地址,对于外网主机来说看到的是NAT后的内网主机地址;而对于outside规则来说,对于内网主机看到的外网地址是NAT后的地址,对于外网主机来说看到的地址是真实的内网地址。\r\n2. inside的规则是给内网主机做nat的,outside的规则是给外网主机做nat的,其实我们可以发现inside和outside是对等的。\r\n3. 静态规则即可以正向内网主动访问外网,也可以反向外网主动访问内网。\r\n\r\n## 2.4. 配置静态PNAT\r\n指定端口进行NAT\r\n```shell\r\nR1(config)#ip nat inside source static tcp 192.168.0.2  23 20.0.0.4 10000\r\n```\r\npc2 通过tcp协议ping 20.0.0.4\r\n```\r\nPC2> ping ?\r\n**ping** HOST [OPTION ...]\r\n  Ping the network HOST. HOST can be an ip address or name\r\n    Options:\r\n     **-1**             ICMP mode, default\r\n     **-2**             UDP mode\r\n     **-3**             TCP mode\r\n     **-c** count       Packet count, default 5\r\n     **-D**             Set the Don't Fragment bit\r\n     **-f** FLAG        Tcp header FLAG |**C**|**E**|**U**|**A**|**P**|**R**|**S**|**F**|\r\n                               bits |7 6 5 4 3 2 1 0|\r\n     **-i** ms          Wait ms milliseconds between sending each packet\r\n     **-l** size        Data size\r\n     **-P** protocol    Use IP protocol in ping packets\r\n                      **1** - ICMP (default), **17** - UDP, **6** - TCP\r\n     **-p** port        Destination port\r\n     **-s** port        Source port\r\n     **-T** ttl         Set ttl, default 64\r\n     **-t**             Send packets until interrupted by Ctrl+C\r\n     **-w** ms          Wait ms milliseconds to receive the response\r\n\r\n  Notes: 1. Using names requires DNS to be set.\r\n         2. Use Ctrl+C to stop the command.\r\n\r\nPC2> ping 20.0.0.4 -P 6 -p 10000 -c 3\r\nConnect   10000@20.0.0.4 seq=1 ttl=62 time=24.921 ms\r\nSendData  10000@20.0.0.4 seq=1 ttl=62 time=33.232 ms\r\nClose     10000@20.0.0.4 seq=1 ttl=62 time=34.103 ms\r\nConnect   10000@20.0.0.4 seq=2 ttl=62 time=35.078 ms\r\nSendData  10000@20.0.0.4 seq=2 ttl=62 time=36.881 ms\r\nClose     10000@20.0.0.4 seq=2 ttl=62 time=25.813 ms\r\nConnect   10000@20.0.0.4 seq=3 ttl=62 time=49.130 ms\r\nSendData  10000@20.0.0.4 seq=3 ttl=62 time=40.901 ms\r\nClose     10000@20.0.0.4 seq=3 ttl=62 time=41.091 ms\r\n```\r\nNAT前目的IP为20.0.0.4,目的端口为10000;NAT后目的ip为192.168.0.2,目的端口为23\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307112319770.png)\r\n\r\n## 2.5. 配置动态NAT\r\n在192.168.0.0/24网络环境中新增pc3,更新组网图如下:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307120026316.png)\r\n### 2.5.1. 配置源地址段为`192.168.0.0/24`的动态nat规则\r\n```shell\r\nR1(config)#ip nat pool mytest 20.0.0.10 20.0.0.20 netmask 255.255.255.0\r\n!定义内部全局地址池\r\nR1(config)#access-list 10 permit 192.168.0.0 0.0.0.255\r\n!定义访问控制列表,内部本地地址范围\r\nR1(config)#ip nat inside source list 10 pool mytest\r\n!建立映射关系\r\n```\r\n#### 2.5.1.1. pc1 ping 30.0.0.2\r\n源地址在192.168.0.0/24内,动态源地址替换为了20.0.0.10\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307112339759.png)\r\n#### 2.5.1.2. pc3 ping 30.0.0.2\r\n源地址在192.168.0.0/24内,动态替换源地址为20.0.0.11\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307112348596.png)\r\n\r\n#### 2.5.1.3. 总结\r\n一个ip只可以被一个内网主机使用,ip不会在主机间复用。\r\n\r\n### 2.5.2. 配置源地址段为`192.168.0.2/32`的动态nat规则\r\n```shell\r\nR1(config)#ip nat pool mytest 20.0.0.10 20.0.0.20 netmask 255.255.255.0\r\n!定义内部全局地址池\r\nR1(config)#access-list 10 permit 192.168.0.2 0.0.0.0\r\n!定义访问控制列表,内部本地地址范围\r\nR1(config)#ip nat inside source list 10 pool mytest\r\n!建立映射关系\r\n```\r\n\r\n#### 2.5.2.1. pc1 ping 30.0.0.2\r\n源地址在192.168.0.2/32内,动态源地址替换为了20.0.0.12\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307112355238.png)\r\n\r\n#### 2.5.2.2. pc3 ping 30.0.0.2\r\n源地址不在192.168.0.2/32内,不替换源地址。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307112354696.png)\r\n\r\n## 2.6. 配置动态PNAT\r\n通过overload参数控制地址重复\r\n```\r\nR1(config)#ip nat pool mytest 20.0.0.10 20.0.0.20 netmask 255.255.255.0\r\n!定义内部全局地址池\r\nR1(config)#access-list 10 permit 192.168.0.0 0.0.0.255\r\n!定义访问控制列表,内部本地地址范围\r\nR1(config)#ip nat inside source list 10 pool mytest overload\r\n!建立映射关系\r\n```\r\n\r\n使用pc1和pc3同时ping 30.0.0.2,可以发现复用了同一个20.0.0.12地址\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202307120013041.png)\r\n"},{"id":"(十)NAT穿透","title":"(十)NAT穿透","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"十nat穿透","description":"","relativePath":"Tech/Network/NAT/(十)NAT穿透.md","rawContent":""},{"id":"NAT高可靠","title":"NAT高可靠","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"nat高可靠","description":"1. 双机热备 双机热备对于防火墙来说是一个必不可少的功能,两台防火墙上的NAT配置也需要完全相同,这样就会出现一个问题:如果两个防火墙分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了地址池优先级的概念。 在双机热备的环境中,如果地址池被配置为高优先级...","relativePath":"Tech/Network/NAT/NAT高可靠.md","rawContent":"# 1. 双机热备\r\n双机热备对于防火墙来说是一个必不可少的功能,两台防火墙上的NAT配置也需要完全相同,这样就会出现一个问题:如果两个防火墙分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了地址池优先级的概念。\r\n\r\n在双机热备的环境中,如果地址池被配置为高优先级,在进行端口映射的时候端口取值范围为1024~35000;如果地址池被配置为低优先级,其端口取值范围为35001~65535。这样主备两台防火墙虽然使用相同的NAT地址池中的地址,但是由于地址池的优先级不同,所以就不会出现NAT转换后公网IP和公网端口完全相同的情况了。\r\n\r\n\r\n\r\n| 故障场景 | 双层架构 | 三层架构 |\r\n| ------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------- |\r\n| forwarder单节点故障 | 流量hash到新forwarder节点,director需要承担单节点会话丢失的瞬时upcall | 流量hash到新forwarder节点,director需要承担单节点会话丢失的瞬时upcall |\r\n| forwarder可用区故障 | 流量hash到另一个可用区的forwarder节点,director需要承受1/2总流量的瞬时upcall | flow master需要承担单节点会话丢失瞬时的upcall |\r\n| director故障 | 存量会话不受影响,丢失一半新建能力 | 存量会话不受影响,丢失一半新建能力 |\r\n| flow master故障 | 不涉及 | 无影响,另一个flow master承担会话缓存能力 |\r\n| | | |\r\n\r\n# 2. 分布式源端NAT\r\n典型的是google cloud nat的思路,每个源端节点分配transit_ip的部分端口,相当于把两节点主主式的nat网关节点进一步扩大到N个节点主主式的nat网关。节点的端口段分配策略采用静态分配和动态分配两种。"},{"id":"流日志","title":"流日志","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"流日志","description":"1. 格式 1.1. 网络信息7元组 internalip,internalport,externalip,externalport,transitip,transitport,protocol 1.2. 流量数据 packetnum,bytesize 1.3. 时间信息 starttime,end...","relativePath":"Tech/Network/NAT/流日志.md","rawContent":"# 1. 格式\r\n\r\n## 1.1. 网络信息7元组\r\ninternal_ip,internal_port,external_ip,external_port,transit_ip,transit_port,protocol\r\n## 1.2. 流量数据\r\npacket_num,byte_size\r\n\r\n## 1.3. 时间信息\r\nstart_time,end_time\r\n\r\n## 1.4. 网元信息\r\n网关id,匹配规则信息\r\n\r\n# 2. google\r\n由于google是采用分布式nat的方案,因此google流日志功能是在虚拟机中实现的。\r\n# 3. 参考链接\r\n[Flow log record examples - Amazon Virtual Private Cloud](https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-records-examples.html#flow-log-example-nat)\r\nhttps://cloud.google.com/nat/docs/monitoring?hl=zh-cn"},{"id":"Network","title":"Network","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"network","description":"","relativePath":"Tech/Network/Network.md","rawContent":""},{"id":"IP分片报文和TCP分段报文","title":"IP分片报文和TCP分段报文","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":10,"slug":"ip分片报文和tcp分段报文","description":"分组可以发生在传输层和网络层,传输层中的TCP会分段,网络层中的IP会分片。IP层的分片更多的是为传输层的UDP服务的,由于TCP自己会避免IP的分片,所以使用TCP传输在IP层都不会发生分片的现象。我们在学习TCP/IP协议时都知道,TCP报文段如果很长的话,会在发送时发生分段,在接受时进行重组,...","relativePath":"Tech/Network/传输层/IP分片报文和TCP分段报文.md","rawContent":"分组可以发生在传输层和网络层,传输层中的TCP会分段,网络层中的IP会分片。IP层的分片更多的是为传输层的UDP服务的,由于TCP自己会避免IP的分片,所以使用TCP传输在IP层都不会发生分片的现象。我们在学习TCP/IP协议时都知道,TCP报文段如果很长的话,会在发送时发生分段,在接受时进行重组,同样IP数据报在长度超过一定值时也会发生分片,在接收端再将分片重组。传输层分段的主要控制参数是最大分段大小MSS,网络层分片的主要控制参数是最大传输单元MTU。\r\n\r\n# 1. 最大传输单元(MTU)\r\nIP数据报在互联网上传输时,可能要经过多个物理网络才能从源端传输到目的端。不同的网络由于链路层和介质的物理特性不同,因此在进行数据传输时,对数据帧的最大长度都有一个限制,这个限制值即最大传输单元MTU(Maximum Transmission Unit)。MTU是链路层中的网络对数据帧的一个限制,以以太网为例,MTU为1500个字节。一个IP数据报在以太网中传输,如果它的长度大于该MTU值,就要进行分片传输,使得每片数据报的长度小于MTU。分片传输的IP数据报不一定按序到达,但IP首部中的信息能让这些数据报片按序组装 (标识、标志、偏移)。IP数据报的分片与重组是在网络层进完成的。\r\n\r\n同一个网络上的两台主机之间通信时,该网络的MTU值是确定的,一般不存在分片问题。分片问题一般只存在于具有不同MTU值的互联网中。由于现在互联网主要使用路由器进行网络连接,因此分片工作通常由路由器负责。当两台主机之间的通信要通过多个具有不同MTU值的网络时,MTU的瓶颈是通信路径上最小的MTU值,它被称为路径MTU。由于路由选择不一定是对称的(从A到B的路由可能与从B到A的路由不同),因此,路径MTU在两个方向上不一定是一致的,下表是几种常用网络的MTU值:\r\n\r\n| 网络名称 | MTU(字节) |\r\n| --------------- | --------- |\r\n| 以太网 | 1500 |\r\n| IEEE802.3/802.2 | 1492 |\r\n| FDDI | 4352 |\r\n| X.25 | 576 |\r\n\r\n# 2. MSS(最大分段大小)\r\nMSS是TCP里的一个概念(首部的选项字段中)。MSS是TCP数据包每次能够传输的最大数据分段,TCP报文段的长度大于MSS时,要进行分段传输。TCP协议在建立连接的时候通常要协商双方的MSS值,每一方都有用于通告它期望接收的MSS选项(MSS选项只出现在SYN报文段中,即TCP三次握手的前两次)。MSS的值一般为MTU值减去两个首部大小(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)。所以如果用链路层以太网,MSS的值往往为**1460**。而Internet上标准的MTU(最小的MTU,链路层网络为x.25协议时)为**576**,那么如果不设置,则MSS的默认值就为536个字节。TCP报文段的分段与重组是在传输层完成的。\r\n\r\n# 3. 分片\r\n很显然,TCP分段的原因是MSS,IP分片的原因是MTU,由于一直有MSS<=MTU,很明显,分段后的每一段TCP报文段再加上IP首部后的长度不可能超过MTU,因此也就不需要在网络层进行IP分片了。因此TCP报文段很少会发生IP分片的情况。再来看UDP数据报,由于UDP数据报不会自己进行分段,因此当长度超过了MTU时,会在网络层进行IP分片。同样,ICMP也会出现IP分片情况。\r\n\r\n在TCP/IP分层中,数据链路层用MTU(Maximum Transmission Unit,最大传输单元)来限制所能传输的数据包大小,MTU是指一次传送的数据最大长度,不包括数据链路层数据帧的帧头,如以太网的MTU为1500字节,实际上数据帧的最大长度为`1518=6+6+2+4+1500`字节,其中以太网数据帧的帧头为`6+6+2`字节。当一个IP数据报封装成链路层的帧时,此IP数据报的总长度 (首部加上数据部分)一定不能超过下面的数据链路层的MTU值,否则要分片,把一个IP数据报为了适合网络传输而分成多个数据报的过程称为分片,被分片后的各个IP数据报可能经过不同的路径到达目标主机。一个IP数据报在传输过程中可能被分片,也可能不被分片。如果被分片,分片后的IP数据报和原来没有分片的IP数据报结构是相同的,即也是由IP头部和IP数据区两个部分组成。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/ip%E5%88%86%E7%89%87%E6%8A%A5%E6%96%87.png)\r\n\r\n分片后的IP数据报,数据区是原IP数据报数据区的一个连续部分,头部是原IP数据报头部的复制,但与原来未分片的IP数据报头部有两点主要不同:**标志和片偏移**。由于分片报文是对IP数据部分的拆分,因此三层以上的头部信息(例如传输层的端口)只存在于IP报文的第一个分片。\r\n\r\n# 4. 重组\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200425203453.png)\r\n\r\n当分了片的IP数据报到达最终目标主机时,目标主机对各分片进行组装,恢复成源主机发送时的IP数据报,这个过程叫做IP数据报的重组。\r\n- 在IP数据报头部中,标识用2个字节表示,它唯一地标识主机发送的每一份数据报。在一个数据报被分片时,每个分片把数据报“标识”字段的值原样复制一份,所以一个数据报的所有分片具有相同的标识。标识是由源主机生成的,源主机可以使用不同的方法来生成标识符,比如递增计数器、随机数、哈希函数等。标识的生成方法应该尽量保证在一定时间内不重复,以避免与其他数据包混淆。同时,标识的生成方法应该尽量简单和高效,以减少开销和延迟。\r\n- 标志字段共3位。其中第一位未使用,第二位**DF**为“不分段”标志位,值为1时表示不允许路由器分割该数据段。第三位MF表示“更多的段”,除了最后一个段以外,其他所有的段都必须设置为1,用途是接受方可以知道什么时候一个数据包的所有数据分段都已经到达了。\r\n- 片偏移指明了当前数据分段在完整数据包中的位置(单位字节),比如片偏移为185,则代表该片在完整数据包的偏移是`185*8=1480`字节。片偏移共13位,因此数据包最多有`2^13=8192`个段。\r\n\r\n目标端主机重组数据报的原理是:\r\n1. 根据“标识”字段可以确定收到的分片属于原来哪个IP数据报;\r\n2. 根据“标志”字段的“片未完MF”子字段可以确定分片是不是最后一个分片;\r\n3. 根据“偏移量”字段可以确定分片在原数据报中的位置。\r\n"},{"id":"SCTP","title":"SCTP","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"sctp","description":"SCTP ( Stream Control Transmission Protocol ),即流媒体控制传输协议,是一种可靠的基于无连接数据包网络如IP网络之上传输协议。他被设计用来在IP网络上传输PSTN在窄带信令消息,同时也能支持宽带信令消息的传输。 SCTP可以看作OSI层次结构中的传输层,它...","relativePath":"Tech/Network/传输层/SCTP.md","rawContent":"SCTP ( Stream Control Transmission Protocol ),即流媒体控制传输协议,是一种可靠的基于无连接数据包网络如IP网络之上传输协议。他被设计用来在IP网络上传输PSTN在窄带信令消息,同时也能支持宽带信令消息的传输。\r\n\r\nSCTP可以看作OSI层次结构中的传输层,它的上层作为SCTP用户应用,下层为分组网络IP层。\r\n\r\n\r\n# 1. 参考链接\r\n[SCTP通用报文格式 (023wg.com)](http://www.023wg.com/message/message/cd_feature_sctp_general.html)\r\n[传输层 - IP报文格式大全(html) - 华为 (huawei.com)](https://support.huawei.com/enterprise/zh/doc/EDOC1100174722?section=j006)"},{"id":"Tcp KeepAlive","title":"Tcp KeepAlive","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"tcp-keepalive","description":"1. 起源 连接有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接。长连接环境下,双方建立交互的连接并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现: 1. 主机突然掉电、死机、异常重启 2. 中间路由...","relativePath":"Tech/Network/传输层/Tcp KeepAlive.md","rawContent":"# 1. 起源\r\n\r\n`TCP`连接有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接。长连接环境下,双方建立交互的连接并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现:\r\n1. 主机突然掉电、死机、异常重启\r\n2. 中间路由网络无故断开\r\n3. 中间防火墙设备会话超时丢弃会话\r\n4. 网络隔离\r\n> 使用kill -9强制杀死进程后,并不会出现连接不释放的问题。TCP连接还是可以正常释放的,因为内核OS代替发送了FIN报文。\r\n\r\n当这些意外发生之后,这些`TCP`连接并未来得及正常释放,那么,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用`TCP`的保活报文来实现,这就有了TCP的Keepalive(保活探测)机制。目前包括`Windows/Macos/Linux`在内的大多数主流OS都已经实现了TCP的Keepalive功能。\r\n\r\n# 2. 默认参数\r\n- tcp keepalive time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个keepalive报文的间隔,默认7200秒\r\n- tcp keepalive interval,如keepalive报文没有应答,则在keepalive间隔后再次发送keepalive报文,默认75秒\r\n- tcp keepalive count,在没有接受到对方确认,继续发送保活探测保次数,默认9次\r\nidle连接需要经过最长`keepalive_time + keepalive_interval*keepalive_count = 7200 + 75 * 9 = 7875` 才能断开idle连接。\r\n# 3. 配置方式\r\n```shell\r\n# On Linux, this is done by editing the /etc/sysctl.conf file:\r\n# detect dead connections after 70 seconds`\r\nnet.ipv4.tcp_keepalive_time = 60\r\nnet.ipv4.tcp_keepalive_intvl = 5\r\nnet.ipv4.tcp_keepalive_probes = 3\r\n\r\nsysctl -p\r\n```\r\n"},{"id":"Tcp重传","title":"Tcp重传","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":14,"slug":"tcp重传","description":"1. tcp超时重传 重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的确认应答报文,就会重发该数据,也就是我们常说的超时重传。 TCP 会在以下两种情况发生超时重传: - 数据包丢失 - 确认应答丢失 1.1. RTT与RTO - RTT(Roud Tri...","relativePath":"Tech/Network/传输层/Tcp重传.md","rawContent":"# 1. tcp超时重传\r\n重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的`ACK`确认应答报文,就会重发该数据,也就是我们常说的**超时重传**。\r\n\r\nTCP 会在以下两种情况发生超时重传:\r\n- 数据包丢失\r\n- 确认应答丢失\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321195920058.png)\r\n## 1.1. RTT与RTO\r\n- RTT(Roud Trip Time): 往返时延,从数据包发出去到接收到ACK的时间。RTT是连接粒度的,每个连接都有一个独立的RTT\r\n- RTO(Retransmission Time Out):重传时间\r\n![[性能指标#7. 往返时间(Round-Trip Time,RTT)]]\r\n\r\n\r\n超时重传时间是以 `RTO` (Retransmission Timeout 超时重传时间)表示。\r\n> [!question]\r\n> 假设在重传的情况下,超时时间 `RTO` 「较长或较短」时,会发生什么事情呢?\r\n- 当超时时间 **RTO 较大**时,重发就慢,丢了老半天才重发,没有效率,性能差;\r\n- 当超时时间 **RTO 较小**时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321200710916.png)\r\n\r\n精确的测量超时时间 `RTO` 的值是非常重要的,这可让我们的重传机制更高效,超时重传时间 RTO 的值应该略大于报文往返 RTT 的值。\r\n## 1.2. RTT经典计算方案\r\n最初的规范[RFC0793](https://www.rfc-editor.org/rfc/rfc793#section-2.8)采用了加权滑动平均来估算RTT,即SRTT(Smoothed Round Trip Time)\r\n```\r\nSRTT = ( ALPHA * SRTT ) + ((1-ALPHA) * RTT)\r\n```\r\nRTO计算如下,其中`UBOUND`是timeout的上限(1min),LBOUND是timeout的下限(1s),ALPHA是加权因子(0.8-0.9),BETA是时延方差系数(1.3-2)\r\n```\r\nRTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]]\r\n```\r\n### 1.2.1. 存在的问题\r\n#### 1.2.1.1. 二义性\r\n[RFC1122](https://link.zhihu.com/?target=https%3A//tools.ietf.org/html/rfc1122%23page-95) 指出当出现数据包重传的情况下,RTT的计算会出现二义性。在测量RTT样本的过程中若一个包的传输出现超时,该数据就会被重传,接着收到一条ACK信息,那么该ACK是对第一条还是第二条传输的确认就存在这二义性。对于客户端来说,它不知道发生了哪种情况,选错情况的结果就是RTT偏大/偏小,影响到RTO的计算。(最简单粗暴的解决方法就是忽略有重传的数据包,只计算那些没重传过的,但这样会导致在网络环境突然恶化的场景下,大量重传数据包没有被用来计算RTT,导致RTO无法更新,进而导致每一个分片报文都一直被重传,详见 [Karn's algorithm](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Karn%2527s_algorithm))\r\n#### 1.2.1.2. 网络性能恶化加剧\r\nRTT的加权平均算法,ALPHA系数一般都比较大,本质上是一个低通滤波器,对突然的网络波动不敏感,如果网络时延突然增加会导致实际RTT值大于预估值,导致不必要的重传,加重网络负担。\r\n## 1.3. RTT最新方案\r\n[RFC6298](https://link.zhihu.com/?target=https%3A//tools.ietf.org/html/rfc6298) 提出基于滑动平均值SRTT和平均偏差RTTV(Round-Trip-Time variation)来综合计算,在大数据实验的基础上通过调参得到一个控制系数`K`,一般为4。此外,该算法假设系统时间粒度为`G`秒,即差值的最小值。\r\n1. RTO设置为LOUND,即1s\r\n2. 第一个RTT(R)计算出来\r\n```\r\nSRTT=R\r\nRTTVAR=R/2\r\nRTO=SRTT + max(G, K * RTTVAR)\r\n```\r\n3. 对于后续每一个RTT(R')\r\n ```\r\nRTTVAR = (1 - beta) * RTTVAR + beta * |SRTT - R'|\r\nSRTT = (1 - alpha) * SRTT + alpha * R'\r\nRTO = SRTT + max(G, K * RTTVAR)\r\n ```\r\n## 1.4. 重传机制\r\n如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是**超时间隔加倍**。也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。tcp_retries2用于控制tcp重传的次数,若发生网络中断,预估需要15min以上才会放弃连接。\r\n```shell\r\nroot@vultr:~# cat /proc/sys/net/ipv4/tcp_retries2\r\n15\r\nroot@vultr:~# sysctl net.ipv4.tcp_retries2\r\nnet.ipv4.tcp_retries2 = 15\r\n```\r\n# 2. 快速重传\r\nTCP 还有另外一种**快速重传(Fast Retransmit)机制**,它**不以时间为驱动,而是以数据驱动重传**。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321204549278.png)\r\n在上图,发送方发出了 1,2,3,4,5 份数据:\r\n- 第一份 Seq1 先送到了,于是就 Ack 回 2;\r\n- 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;\r\n- 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;\r\n- 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。\r\n- 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。\r\n> [!note]\r\n快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。\r\n# 3. SACK\r\n快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是**重传的时候,是重传之前的一个,还是重传所有的问题。**\r\n\r\n比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端并不清楚这连续的三个Ack 2是谁传回来的,以上两种情况都是有可能的。可见,这是一把双刃剑。为了解决不知道该重传哪些 TCP 报文,于是就有 `SACK` 方法。\r\n\r\n这种方式需要在 TCP 头部「选项」字段里加一个 `SACK` 的东西,它可以将**已经接收到缓存队列的seq信息**发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以**只重传丢失的数据**。\r\n\r\n如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 `SACK` 信息发现只有 `200~299` 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321205036819.png)\r\n> [!note]\r\n如果要支持 `SACK`,必须双方都要支持。在 Linux 下,可以通过 `net.ipv4.tcp_sack` 参数打开这个功能(Linux 2.4 后默认打开)。\r\n\r\n```shell\r\nroot@vultr:~# sysctl net.ipv4.tcp_sack\r\nnet.ipv4.tcp_sack = 1\r\n```\r\n# 4. Duplicate SACK\r\nDuplicate SACK 又称 `D-SACK`,其主要使用了SACK来告诉「发送方」有哪些数据被重复接收了。\r\n> [!note]\r\n> sack表现是ack小与sack,代表丢数据了,但是我可以提前ack后面的;D-sack表现是ack大于sack,说明数据重复了,你发给我的数据我已经ack过了。\r\n## 4.1. ACK丢包\r\n- 「接收方」发给「发送方」的两个 ACK 确认应答都丢失了,所以发送方超时后,重传第一个数据包(3000 ~ 3499)\r\n- 「接收方」发现数据是重复收到的,于是回了一个 SACK = 3000~3500,告诉「发送方」 3000~3500 的数据早已被接收了,因为ACK都到了 4000 了,已经意味着 4000之前的所有数据都已收到,所以这个 SACK 就代表着 `D-SACK`。\r\n- 这样「发送方」就知道了,数据没有丢,是「接收方」的 ACK 确认报文丢了。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321205427398.png)\r\n## 4.2. 网络时延\r\n- 数据包(1000~1499) 被网络延迟了,导致「发送方」没有收到 Ack 1500 的确认报文。\r\n- 而后面报文到达的三个相同的 ACK 确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包(1000~1499)又到了「接收方」;\r\n- **所以「接收方」回了一个 SACK=1000~1500,因为 ACK 已经到了 3000,所以这个 SACK 是 D-SACK,表示收到了重复的包。**\r\n- 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK 包丢了,而是因为网络延迟了。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321205913392.png)\r\n## 4.3. 优势\r\n在TCP重传场景下,Duplicate SACK (D-SACK) 是TCP选择性确认 (SACK) 机制的一个扩展,具有以下重要作用:\r\n1. **检测重复数据包接收**:D-SACK允许接收方通知发送方它收到了重复的数据段,无论这些重复段是否在接收方的当前接收窗口内。\r\n2. **区分网络问题类型**:\r\n - 帮助发送方区分是网络延迟导致的数据包重排,还是数据包丢失\r\n - 区分数据包被网络复制还是发送方重传导致的重复\r\n3. **改进拥塞控制**:\r\n - 通过正确识别非拥塞丢包情况,避免不必要的拥塞窗口缩小\r\n - 当重复包是由网络延迟而非丢包引起时,可以避免错误的重传超时调整\r\n4. **优化重传策略**:\r\n - 减少不必要的重传,提高网络带宽利用率\r\n - 允许更精确地计算往返时间(RTT)\r\n5. **提高网络性能**:\r\n - 特别是在无线网络等高丢包环境中效果显著\r\n - 减少由于错误解读网络状况而导致的性能下降\r\nD-SACK是在RFC 2883中定义的,它通过允许接收方报告重复收到的数据段,使TCP协议能够更智能地应对网络中的各种异常情况,从而提高网络传输效率和可靠性。\r\n> [!note]\r\n在 Linux 下可以通过 `net.ipv4.tcp_dsack` 参数开启/关闭这个功能(Linux 2.4 后默认打开)\r\n# 5. tcp user timeout\r\n可以针对不同应用设置超时时间,当数据发送超过设置时间后仍然没有ack,或者迟迟读不到数据,就放弃链接。tcp user timouet可以使用setsockopt函数实现。\r\n```c\r\n#include \r\nint setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);\r\n```\r\n- **sockfd**:套接字描述符,指定要设置选项的套接字。\r\n- **level**:协议级别,指定选项所在的协议层。常见值包括:\r\n - `SOL_SOCKET`:套接字层级选项\r\n - `IPPROTO_TCP`:TCP协议选项\r\n - `IPPROTO_IP`:IP协议选项\r\n - `IPPROTO_IPV6`:IPv6协议选项\r\n- **optname**:选项名称,指定要设置的特定选项。不同协议层有不同的选项,例如:\r\n - `SO_REUSEADDR`、`SO_KEEPALIVE`(套接字层)\r\n - `TCP_NODELAY`、`TCP_USER_TIMEOUT`(TCP层)\r\n - `IP_TTL`(IP层)\r\n- **optval**:选项值的指针,指向包含新选项值的缓冲区。\r\n- **optlen**:`optval` 缓冲区的大小(以字节为单位)。\r\n## 5.1. 控制tcp连接超时时间\r\n具体来说,TCP_USER_TIMEOUT定义了TCP在放弃连接之前等待未确认数据的最长时间。当一个TCP连接**已经建立**,并且发送方发送了数据但长时间没有收到确认(ACK)时,这个参数决定了发送方等待多久后认为连接失效。\r\n- **不控制建立连接的超时**:TCP 连接建立(三次握手)的超时通常由系统内核参数如 `tcp_syn_retries` 控制,或者在应用程序中通过 `connect()` 调用的超时机制控制。\r\n- **控制数据传输过程中的超时**:当连接**已建立**,并且有未确认的数据时才生效。\r\n```c\r\nint timeout = 30000; // 30秒(毫秒为单位)\r\nsetsockopt(sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));\r\n```\r\n## 5.2. 用途\r\n主要用于检测网络异常导致的对端无响应情况,特别是在长连接应用中,可以更早地检测到连接断开,而不是一直等待TCP的默认重传机制耗尽,在java应用中可以通过设置[[数据库关键配置#1. socketTimeout|socketTimeout]]控制。\r\n# 6. ref\r\n[tcp重传时间 tcp_retries2 tcp 重传机制](https://www.cnblogs.com/zafu/p/18266870)"},{"id":"nat封装pp协议","title":"Nat封装Pp协议","date":"2025-09-17T13:31:12.000Z","tags":["excalidraw"],"readingTime":1,"slug":"nat封装pp协议","description":"==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements client ^pdsoIL0z NAT ^lEtOOR5s Server ^7HyKnicB SYN seq0 ^3...","relativePath":"Tech/Network/其他/nat封装pp协议.md","rawContent":"---\r\n\r\nexcalidraw-plugin: parsed\r\ntags: [excalidraw]\r\n\r\n---\r\n==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==\r\n\r\n\r\n# Text Elements\r\nclient ^pdsoIL0z\r\n\r\nNAT ^lEtOOR5s\r\n\r\nServer ^7HyKnicB\r\n\r\nSYN\r\nseq0 ^3wBZM9az\r\n\r\nSYN\r\nseq0 ^R34LJTTX\r\n\r\nSYN ACK\r\nseq1\r\n ack seq0+1 ^ku3h4ofD\r\n\r\nSYN ACK\r\nseq1\r\nack seq0+1 ^VaSJirUE\r\n\r\nACK\r\nse0+1\r\nack seq1+1 ^5yzdFJfC\r\n\r\nACK\r\nseq0+1\r\nack seq1+1 ^uJx2rLkd\r\n\r\nPSH ACK\r\nseq0+1\r\nack seq1+1 ^skKSIqjO\r\n\r\nPP\r\nseq0+1\r\nack seq+1 ^MMMDiihE\r\n\r\nPSH ACK\r\nseq0+1+pp_len\r\nack seq1+1 ^uUkvWLB5\r\n\r\n在发送第一个数据报文前,发送pp数据包 ^LV7br3Ql\r\n\r\n用户的数据包中的seq需要额外加上pp_len的长度 ^b9Iy3zcE\r\n\r\nACK\r\nseq1+1\r\nack seq0+1+pp_len ^0DvdQori\r\n\r\nACK\r\nseq1+1\r\nack seq0+len+1 ^bv0yq8Fc\r\n\r\nACK\r\nseq1+2\r\nack seq0+1+pp_len+len ^L4xd5TyH\r\n\r\n%%\r\n# Drawing\r\n```json\r\n{\r\n\t\"type\": \"excalidraw\",\r\n\t\"version\": 2,\r\n\t\"source\": \"https://excalidraw.com\",\r\n\t\"elements\": [\r\n\t\t{\r\n\t\t\t\"type\": \"line\",\r\n\t\t\t\"version\": 113,\r\n\t\t\t\"versionNonce\": 1032280207,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"m3iF9m4FE5h3X6e0NLHGo\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": -200,\r\n\t\t\t\"y\": -180,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 0,\r\n\t\t\t\"height\": 780,\r\n\t\t\t\"seed\": 81634927,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": null,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t780\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"line\",\r\n\t\t\t\"version\": 253,\r\n\t\t\t\"versionNonce\": 1134787553,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"nlPzc8VJPtpiIHzRuinNr\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 0,\r\n\t\t\t\"y\": -180,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 0,\r\n\t\t\t\"height\": 780,\r\n\t\t\t\"seed\": 1638577729,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": null,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t780\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"line\",\r\n\t\t\t\"version\": 269,\r\n\t\t\t\"versionNonce\": 1464561327,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"YEG-0OkChANzMuw0Z9J82\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 220,\r\n\t\t\t\"y\": -180,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 0,\r\n\t\t\t\"height\": 780,\r\n\t\t\t\"seed\": 1755827489,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": null,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t780\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 18,\r\n\t\t\t\"versionNonce\": 678123457,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"pdsoIL0z\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": -180,\r\n\t\t\t\"y\": -220,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 48,\r\n\t\t\t\"height\": 23,\r\n\t\t\t\"seed\": 1723098049,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 20,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"client\",\r\n\t\t\t\"rawText\": \"client\",\r\n\t\t\t\"baseline\": 19,\r\n\t\t\t\"textAlign\": \"left\",\r\n\t\t\t\"verticalAlign\": \"top\",\r\n\t\t\t\"containerId\": null,\r\n\t\t\t\"originalText\": \"client\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 31,\r\n\t\t\t\"versionNonce\": 1751630031,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"lEtOOR5s\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": -20,\r\n\t\t\t\"y\": -220,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 40,\r\n\t\t\t\"height\": 23,\r\n\t\t\t\"seed\": 411947681,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 20,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"NAT\",\r\n\t\t\t\"rawText\": \"NAT\",\r\n\t\t\t\"baseline\": 19,\r\n\t\t\t\"textAlign\": \"left\",\r\n\t\t\t\"verticalAlign\": \"top\",\r\n\t\t\t\"containerId\": null,\r\n\t\t\t\"originalText\": \"NAT\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 23,\r\n\t\t\t\"versionNonce\": 59553697,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"7HyKnicB\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 140,\r\n\t\t\t\"y\": -220,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 60,\r\n\t\t\t\"height\": 23,\r\n\t\t\t\"seed\": 121843041,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 20,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"Server\",\r\n\t\t\t\"rawText\": \"Server\",\r\n\t\t\t\"baseline\": 19,\r\n\t\t\t\"textAlign\": \"left\",\r\n\t\t\t\"verticalAlign\": \"top\",\r\n\t\t\t\"containerId\": null,\r\n\t\t\t\"originalText\": \"Server\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"version\": 20,\r\n\t\t\t\"versionNonce\": 1383386863,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"091QgiAn4BgPzzu1P4SOf\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": -200,\r\n\t\t\t\"y\": -160,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 200,\r\n\t\t\t\"height\": 43,\r\n\t\t\t\"seed\": 468416719,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"3wBZM9az\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\",\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t200,\r\n\t\t\t\t\t43\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 15,\r\n\t\t\t\"versionNonce\": 1463229313,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"3wBZM9az\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": -98,\r\n\t\t\t\"y\": -156,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 36,\r\n\t\t\t\"height\": 37,\r\n\t\t\t\"seed\": 656757519,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"SYN\\nseq0\",\r\n\t\t\t\"rawText\": \"SYN\\nseq0\",\r\n\t\t\t\"baseline\": 34,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"containerId\": \"091QgiAn4BgPzzu1P4SOf\",\r\n\t\t\t\"originalText\": \"SYN\\nseq0\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"version\": 25,\r\n\t\t\t\"versionNonce\": 804985103,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"Kl1DWtKqsz1BExsZ4BdiD\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 0,\r\n\t\t\t\"y\": -120,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 220,\r\n\t\t\t\"height\": 40,\r\n\t\t\t\"seed\": 682529505,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"R34LJTTX\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\",\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t220,\r\n\t\t\t\t\t40\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 13,\r\n\t\t\t\"versionNonce\": 866780001,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"R34LJTTX\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 62,\r\n\t\t\t\"y\": -119,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 36,\r\n\t\t\t\"height\": 38,\r\n\t\t\t\"seed\": 1388412111,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"SYN\\nseq0\",\r\n\t\t\t\"rawText\": \"SYN\\nseq0\",\r\n\t\t\t\"baseline\": 34,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"containerId\": \"Kl1DWtKqsz1BExsZ4BdiD\",\r\n\t\t\t\"originalText\": \"SYN\\nseq0\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"version\": 31,\r\n\t\t\t\"versionNonce\": 913514287,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"PgM1d1K7TWnTzGUjpFETl\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 220,\r\n\t\t\t\"y\": -60,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 220,\r\n\t\t\t\"height\": 60,\r\n\t\t\t\"seed\": 388453775,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"ku3h4ofD\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\",\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t-220,\r\n\t\t\t\t\t60\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 95,\r\n\t\t\t\"versionNonce\": 1966403393,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"ku3h4ofD\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 49.5,\r\n\t\t\t\"y\": -58.5,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 101,\r\n\t\t\t\"height\": 57,\r\n\t\t\t\"seed\": 11136175,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"SYN ACK\\nseq1\\n ack seq0+1\",\r\n\t\t\t\"rawText\": \"SYN ACK\\nseq1\\n ack seq0+1\",\r\n\t\t\t\"baseline\": 53,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"containerId\": \"PgM1d1K7TWnTzGUjpFETl\",\r\n\t\t\t\"originalText\": \"SYN ACK\\nseq1\\n ack seq0+1\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"version\": 17,\r\n\t\t\t\"versionNonce\": 1480941903,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"jXDHyzzS1Nv1w7QQgzhrQ\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 0,\r\n\t\t\t\"y\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 200,\r\n\t\t\t\"height\": 60,\r\n\t\t\t\"seed\": 820767841,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"VaSJirUE\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\",\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t-200,\r\n\t\t\t\t\t60\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 52,\r\n\t\t\t\"versionNonce\": 1110925089,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"VaSJirUE\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": -141.5,\r\n\t\t\t\"y\": 1.5,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 83,\r\n\t\t\t\"height\": 56,\r\n\t\t\t\"seed\": 2015618863,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"SYN ACK\\nseq1\\nack seq0+1\",\r\n\t\t\t\"rawText\": \"SYN ACK\\nseq1\\nack seq0+1\",\r\n\t\t\t\"baseline\": 52,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"containerId\": \"jXDHyzzS1Nv1w7QQgzhrQ\",\r\n\t\t\t\"originalText\": \"SYN ACK\\nseq1\\nack seq0+1\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"version\": 15,\r\n\t\t\t\"versionNonce\": 650796911,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"xcpjY_ot7TvgA2vdD5RS1\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": -200,\r\n\t\t\t\"y\": 80,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 200,\r\n\t\t\t\"height\": 60,\r\n\t\t\t\"seed\": 503602369,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"5yzdFJfC\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\",\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t200,\r\n\t\t\t\t\t60\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 31,\r\n\t\t\t\"versionNonce\": 1226887937,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"5yzdFJfC\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": -141.5,\r\n\t\t\t\"y\": 81.5,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 83,\r\n\t\t\t\"height\": 56,\r\n\t\t\t\"seed\": 1225076801,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349967,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"ACK\\nse0+1\\nack seq1+1\",\r\n\t\t\t\"rawText\": \"ACK\\nse0+1\\nack seq1+1\",\r\n\t\t\t\"baseline\": 52,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"containerId\": \"xcpjY_ot7TvgA2vdD5RS1\",\r\n\t\t\t\"originalText\": \"ACK\\nse0+1\\nack seq1+1\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"version\": 16,\r\n\t\t\t\"versionNonce\": 1951046031,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"-ZL01wsvcyUuI1vBsMiF0\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 20,\r\n\t\t\t\"y\": 160,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 200,\r\n\t\t\t\"height\": 60,\r\n\t\t\t\"seed\": 1281048897,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"uJx2rLkd\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\",\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t200,\r\n\t\t\t\t\t60\r\n\t\t\t\t]\r\n\t\t\t]\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"version\": 38,\r\n\t\t\t\"versionNonce\": 1047579361,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"id\": \"uJx2rLkd\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"x\": 78.5,\r\n\t\t\t\"y\": 161.5,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"width\": 83,\r\n\t\t\t\"height\": 56,\r\n\t\t\t\"seed\": 53995489,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"boundElements\": [],\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"text\": \"ACK\\nseq0+1\\nack seq1+1\",\r\n\t\t\t\"rawText\": \"ACK\\nseq0+1\\nack seq1+1\",\r\n\t\t\t\"baseline\": 52,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"containerId\": \"-ZL01wsvcyUuI1vBsMiF0\",\r\n\t\t\t\"originalText\": \"ACK\\nseq0+1\\nack seq1+1\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"Z8TIIBFwBqJItffKQD5BC\",\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"x\": -200,\r\n\t\t\t\"y\": 160,\r\n\t\t\t\"width\": 200,\r\n\t\t\t\"height\": 60,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"seed\": 1115679009,\r\n\t\t\t\"version\": 12,\r\n\t\t\t\"versionNonce\": 905875375,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"skKSIqjO\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t200,\r\n\t\t\t\t\t60\r\n\t\t\t\t]\r\n\t\t\t],\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"skKSIqjO\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": -141.5,\r\n\t\t\t\"y\": 161.5,\r\n\t\t\t\"width\": 83,\r\n\t\t\t\"height\": 57,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 1487985761,\r\n\t\t\t\"version\": 31,\r\n\t\t\t\"versionNonce\": 448186049,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": null,\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"PSH ACK\\nseq0+1\\nack seq1+1\",\r\n\t\t\t\"rawText\": \"PSH ACK\\nseq0+1\\nack seq1+1\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"baseline\": 53,\r\n\t\t\t\"containerId\": \"Z8TIIBFwBqJItffKQD5BC\",\r\n\t\t\t\"originalText\": \"PSH ACK\\nseq0+1\\nack seq1+1\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"946BNL1fR9ar5RPGABil0\",\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"x\": 0,\r\n\t\t\t\"y\": 220,\r\n\t\t\t\"width\": 220,\r\n\t\t\t\"height\": 60,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"seed\": 1687925263,\r\n\t\t\t\"version\": 27,\r\n\t\t\t\"versionNonce\": 1191747745,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"MMMDiihE\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705846008912,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t220,\r\n\t\t\t\t\t60\r\n\t\t\t\t]\r\n\t\t\t],\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": {\r\n\t\t\t\t\"elementId\": \"LV7br3Ql\",\r\n\t\t\t\t\"focus\": -0.941385435168739,\r\n\t\t\t\t\"gap\": 1\r\n\t\t\t},\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"MMMDiihE\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": 63,\r\n\t\t\t\"y\": 221.5,\r\n\t\t\t\"width\": 74,\r\n\t\t\t\"height\": 57,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 66969025,\r\n\t\t\t\"version\": 35,\r\n\t\t\t\"versionNonce\": 412239521,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": null,\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"PP\\nseq0+1\\nack seq+1\",\r\n\t\t\t\"rawText\": \"PP\\nseq0+1\\nack seq+1\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"baseline\": 53,\r\n\t\t\t\"containerId\": \"946BNL1fR9ar5RPGABil0\",\r\n\t\t\t\"originalText\": \"PP\\nseq0+1\\nack seq+1\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"KYP1U1f_WHlWUSBQx515G\",\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"x\": 0,\r\n\t\t\t\"y\": 280,\r\n\t\t\t\"width\": 219,\r\n\t\t\t\"height\": 50.17069370577428,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"seed\": 894812239,\r\n\t\t\t\"version\": 59,\r\n\t\t\t\"versionNonce\": 30276289,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"uUkvWLB5\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705846020256,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t219,\r\n\t\t\t\t\t50.17069370577428\r\n\t\t\t\t]\r\n\t\t\t],\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": {\r\n\t\t\t\t\"elementId\": \"b9Iy3zcE\",\r\n\t\t\t\t\"focus\": -0.7541282169237863,\r\n\t\t\t\t\"gap\": 1\r\n\t\t\t},\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"uUkvWLB5\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": 54.5,\r\n\t\t\t\"y\": 281.5,\r\n\t\t\t\"width\": 111,\r\n\t\t\t\"height\": 57,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 705625903,\r\n\t\t\t\"version\": 49,\r\n\t\t\t\"versionNonce\": 1325948929,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": null,\r\n\t\t\t\"updated\": 1705845731943,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"PSH ACK\\nseq0+1+pp_len\\nack seq1+1\",\r\n\t\t\t\"rawText\": \"PSH ACK\\nseq0+1+pp_len\\nack seq1+1\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"baseline\": 53,\r\n\t\t\t\"containerId\": \"KYP1U1f_WHlWUSBQx515G\",\r\n\t\t\t\"originalText\": \"PSH ACK\\nseq0+1+pp_len\\nack seq1+1\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"LV7br3Ql\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": 220,\r\n\t\t\t\"y\": 260,\r\n\t\t\t\"width\": 291,\r\n\t\t\t\"height\": 23,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 388796513,\r\n\t\t\t\"version\": 53,\r\n\t\t\t\"versionNonce\": 1586878991,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"id\": \"946BNL1fR9ar5RPGABil0\",\r\n\t\t\t\t\t\"type\": \"arrow\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"在发送第一个数据报文前,发送pp数据包\",\r\n\t\t\t\"rawText\": \"在发送第一个数据报文前,发送pp数据包\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"left\",\r\n\t\t\t\"verticalAlign\": \"top\",\r\n\t\t\t\"baseline\": 17,\r\n\t\t\t\"containerId\": null,\r\n\t\t\t\"originalText\": \"在发送第一个数据报文前,发送pp数据包\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"b9Iy3zcE\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": 220,\r\n\t\t\t\"y\": 320,\r\n\t\t\t\"width\": 347,\r\n\t\t\t\"height\": 23,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 2070088961,\r\n\t\t\t\"version\": 80,\r\n\t\t\t\"versionNonce\": 1688464975,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"id\": \"KYP1U1f_WHlWUSBQx515G\",\r\n\t\t\t\t\t\"type\": \"arrow\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705846025279,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"用户的数据包中的seq需要额外加上pp_len的长度\",\r\n\t\t\t\"rawText\": \"用户的数据包中的seq需要额外加上pp_len的长度\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"left\",\r\n\t\t\t\"verticalAlign\": \"top\",\r\n\t\t\t\"baseline\": 17,\r\n\t\t\t\"containerId\": null,\r\n\t\t\t\"originalText\": \"用户的数据包中的seq需要额外加上pp_len的长度\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"sK_9CSsIObfXp5KCyzwz-\",\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"x\": 220,\r\n\t\t\t\"y\": 360,\r\n\t\t\t\"width\": 220,\r\n\t\t\t\"height\": 80,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"seed\": 2107300527,\r\n\t\t\t\"version\": 59,\r\n\t\t\t\"versionNonce\": 1924010561,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"0DvdQori\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705846025279,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t-220,\r\n\t\t\t\t\t80\r\n\t\t\t\t]\r\n\t\t\t],\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"0DvdQori\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": 39.5,\r\n\t\t\t\"y\": 371.5,\r\n\t\t\t\"width\": 141,\r\n\t\t\t\"height\": 57,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 2109679361,\r\n\t\t\t\"version\": 86,\r\n\t\t\t\"versionNonce\": 1858584065,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": null,\r\n\t\t\t\"updated\": 1705845897608,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"ACK\\nseq1+1\\nack seq0+1+pp_len\",\r\n\t\t\t\"rawText\": \"ACK\\nseq1+1\\nack seq0+1+pp_len\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"baseline\": 53,\r\n\t\t\t\"containerId\": \"sK_9CSsIObfXp5KCyzwz-\",\r\n\t\t\t\"originalText\": \"ACK\\nseq1+1\\nack seq0+1+pp_len\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"pVblaUBH11WWGCU9_zIct\",\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"x\": 0,\r\n\t\t\t\"y\": 500,\r\n\t\t\t\"width\": 200,\r\n\t\t\t\"height\": 80,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"seed\": 166993505,\r\n\t\t\t\"version\": 23,\r\n\t\t\t\"versionNonce\": 501473871,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"bv0yq8Fc\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t-200,\r\n\t\t\t\t\t80\r\n\t\t\t\t]\r\n\t\t\t],\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"bv0yq8Fc\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": -157,\r\n\t\t\t\"y\": 511.5,\r\n\t\t\t\"width\": 114,\r\n\t\t\t\"height\": 57,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 1081383233,\r\n\t\t\t\"version\": 36,\r\n\t\t\t\"versionNonce\": 209546785,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": null,\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"ACK\\nseq1+1\\nack seq0+len+1\",\r\n\t\t\t\"rawText\": \"ACK\\nseq1+1\\nack seq0+len+1\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"baseline\": 53,\r\n\t\t\t\"containerId\": \"pVblaUBH11WWGCU9_zIct\",\r\n\t\t\t\"originalText\": \"ACK\\nseq1+1\\nack seq0+len+1\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"fErEF9rmvj4MQiaTUdvbN\",\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"x\": 220,\r\n\t\t\t\"y\": 440,\r\n\t\t\t\"width\": 220,\r\n\t\t\t\"height\": 60,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"seed\": 109992239,\r\n\t\t\t\"version\": 16,\r\n\t\t\t\"versionNonce\": 384876655,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"type\": \"text\",\r\n\t\t\t\t\t\"id\": \"L4xd5TyH\"\r\n\t\t\t\t}\r\n\t\t\t],\r\n\t\t\t\"updated\": 1705845349968,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t-220,\r\n\t\t\t\t\t60\r\n\t\t\t\t]\r\n\t\t\t],\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"L4xd5TyH\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": 24.5,\r\n\t\t\t\"y\": 441.5,\r\n\t\t\t\"width\": 171,\r\n\t\t\t\"height\": 57,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [\r\n\t\t\t\t\"AZdGx_Evv6aQuvjjQz9d5\"\r\n\t\t\t],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 500192399,\r\n\t\t\t\"version\": 63,\r\n\t\t\t\"versionNonce\": 1842455919,\r\n\t\t\t\"isDeleted\": false,\r\n\t\t\t\"boundElements\": null,\r\n\t\t\t\"updated\": 1705846004790,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"ACK\\nseq1+2\\nack seq0+1+pp_len+len\",\r\n\t\t\t\"rawText\": \"ACK\\nseq1+2\\nack seq0+1+pp_len+len\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"center\",\r\n\t\t\t\"verticalAlign\": \"middle\",\r\n\t\t\t\"baseline\": 53,\r\n\t\t\t\"containerId\": \"fErEF9rmvj4MQiaTUdvbN\",\r\n\t\t\t\"originalText\": \"ACK\\nseq1+2\\nack seq0+1+pp_len+len\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"LX1cU5NO\",\r\n\t\t\t\"type\": \"text\",\r\n\t\t\t\"x\": 231.95261101973682,\r\n\t\t\t\"y\": 279.7216796875,\r\n\t\t\t\"width\": 5,\r\n\t\t\t\"height\": 19,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [],\r\n\t\t\t\"roundness\": null,\r\n\t\t\t\"seed\": 1056830735,\r\n\t\t\t\"version\": 37,\r\n\t\t\t\"versionNonce\": 1677016911,\r\n\t\t\t\"isDeleted\": true,\r\n\t\t\t\"boundElements\": null,\r\n\t\t\t\"updated\": 1705844875526,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"text\": \"\",\r\n\t\t\t\"rawText\": \"\",\r\n\t\t\t\"fontSize\": 16,\r\n\t\t\t\"fontFamily\": 2,\r\n\t\t\t\"textAlign\": \"left\",\r\n\t\t\t\"verticalAlign\": \"top\",\r\n\t\t\t\"baseline\": 15,\r\n\t\t\t\"containerId\": null,\r\n\t\t\t\"originalText\": \"\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": \"qITWz6xeazbBgd-D1Q7WR\",\r\n\t\t\t\"type\": \"arrow\",\r\n\t\t\t\"x\": 200,\r\n\t\t\t\"y\": 300,\r\n\t\t\t\"width\": 200,\r\n\t\t\t\"height\": 40,\r\n\t\t\t\"angle\": 0,\r\n\t\t\t\"strokeColor\": \"#000000\",\r\n\t\t\t\"backgroundColor\": \"transparent\",\r\n\t\t\t\"fillStyle\": \"hachure\",\r\n\t\t\t\"strokeWidth\": 0.5,\r\n\t\t\t\"strokeStyle\": \"solid\",\r\n\t\t\t\"roughness\": 0,\r\n\t\t\t\"opacity\": 100,\r\n\t\t\t\"groupIds\": [],\r\n\t\t\t\"roundness\": {\r\n\t\t\t\t\"type\": 2\r\n\t\t\t},\r\n\t\t\t\"seed\": 751730031,\r\n\t\t\t\"version\": 12,\r\n\t\t\t\"versionNonce\": 2066489409,\r\n\t\t\t\"isDeleted\": true,\r\n\t\t\t\"boundElements\": null,\r\n\t\t\t\"updated\": 1705844885169,\r\n\t\t\t\"link\": null,\r\n\t\t\t\"locked\": false,\r\n\t\t\t\"points\": [\r\n\t\t\t\t[\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0\r\n\t\t\t\t],\r\n\t\t\t\t[\r\n\t\t\t\t\t-200,\r\n\t\t\t\t\t40\r\n\t\t\t\t]\r\n\t\t\t],\r\n\t\t\t\"lastCommittedPoint\": null,\r\n\t\t\t\"startBinding\": null,\r\n\t\t\t\"endBinding\": null,\r\n\t\t\t\"startArrowhead\": null,\r\n\t\t\t\"endArrowhead\": \"arrow\"\r\n\t\t}\r\n\t],\r\n\t\"appState\": {\r\n\t\t\"theme\": \"light\",\r\n\t\t\"viewBackgroundColor\": \"#ffffff\",\r\n\t\t\"currentItemStrokeColor\": \"#000000\",\r\n\t\t\"currentItemBackgroundColor\": \"transparent\",\r\n\t\t\"currentItemFillStyle\": \"hachure\",\r\n\t\t\"currentItemStrokeWidth\": 0.5,\r\n\t\t\"currentItemStrokeStyle\": \"solid\",\r\n\t\t\"currentItemRoughness\": 0,\r\n\t\t\"currentItemOpacity\": 100,\r\n\t\t\"currentItemFontFamily\": 2,\r\n\t\t\"currentItemFontSize\": 16,\r\n\t\t\"currentItemTextAlign\": \"left\",\r\n\t\t\"currentItemStartArrowhead\": null,\r\n\t\t\"currentItemEndArrowhead\": \"arrow\",\r\n\t\t\"scrollX\": 307.63157894736867,\r\n\t\t\"scrollY\": 67.14638157894694,\r\n\t\t\"zoom\": {\r\n\t\t\t\"value\": 0.9500000000000001\r\n\t\t},\r\n\t\t\"currentItemRoundness\": \"round\",\r\n\t\t\"gridSize\": 20,\r\n\t\t\"colorPalette\": {},\r\n\t\t\"previousGridSize\": null\r\n\t},\r\n\t\"files\": {}\r\n}\r\n```\r\n%%"},{"id":"源地址透传","title":"源地址透传","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"源地址透传","description":"1. TOA(TCP Option Address) TOA将源地址放在字段中。option字段最长40字节,每个选项由三部分组成:op-kind、op-length、op-data,我们最常见的MSS字段就是在option里。目前option使用的op-kind并不多,我们只需要构建一个不冲突的o...","relativePath":"Tech/Network/其他/源地址透传.md","rawContent":"# 1. TOA(TCP Option Address)\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/tcp%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F.png)\r\nTOA将源地址放在`tcp option`字段中。option字段最长40字节,每个选项由三部分组成:op-kind、op-length、op-data,我们最常见的MSS字段就是在option里。目前option使用的op-kind并不多,我们只需要构建一个不冲突的op-kind就可以把clientIP填充进去。IPv4 地址占用 4 个字节,[IPv6](https://so.csdn.net/so/search?q=IPv6&spm=1001.2101.3001.7020) 占用 16 字节,填充到 option 中是没有问题的,toa格式如下:\r\n```txt\r\n+----------+----------+--------------------+\r\n| opcode | opsize | port |\r\n+----------+----------+--------------------+\r\n| clientIP |\r\n+------------------------------------------+\r\n```\r\nopcode: 254\r\nopsize: toa大小8字节\r\nport: 客户端端口\r\nclientIP: 客户端IP\r\n# 2. PP(Proxy Protocol)\r\nPP协议原理仅支持tcp协议,原理是在三次握手后,发送请求数据前,在四层头之后插入一个`proxy protocol`数据包,数据包中可以携带`src ip`、`src port`等信息,该协议是由haproxy提出的,目前常见的web服务器都已经支持。\r\n## 2.1. pp协议格式\r\nTCP三次握手建立连接后,发送第一个数据报文的详细流程如下:\r\n1. 客户端向服务器端发送连接请求报文(SYN标志位设置为1,序列号为x)。\r\n2. 服务器向客户端响应报文(SYN和ACK标志位设置为1,序列号为y,确认号为x+1)。\r\n3. 客户端再次响应报文(ACK标志位设置为1,序列号为x+1,确认号为y+1),三次握手完成。\r\n此时TCP连接已建立,客户端可以向服务器发送第一个数据报文了,这个报文中:\r\n- 不会携带SYN标志位\r\n- 会携带ACK标志位\r\n- 序列号为x+1(上次发送的确认号)\r\n- 确认号为y+1(上次接收的确认号)\r\n后续的数据报文都类似,序列号依次增加,确认号也依次增加。所以TCP三次握手完成后发送的第一个数据报文,必须携带ACK标志位,序列号从客户端第三次握手的确认号开始,确认号为服务器第二次握手的确认号+1。这标志着TCP连接已建立,可以开始数据传输了。`PSH`为1的情况,一般只出现在 DATA内容不为0的包中,也就是说PSH为1表示的是有真正的TCP数据包内容被传递。\r\n\r\n![[Tech/Network/其他/nat封装pp协议.md#^group=sK_9CSsIObfXp5KCyzwz-|1000]]\r\n- nat将发送的数据包送到服务端前,将客户端seq号增加pp_len偏移。\r\n- nat将接受数据包发送到客户端前,将ack的seq号减去pp_len。\r\n\r\npp协议中具体数据格式如下:\r\n```text\r\n+----------+--------------------+----------+\r\n| type | Length | value |\r\n+----------+----------+---------+----------+\r\n| value |\r\n+------------------------------------------+\r\n```"},{"id":"DHCP协议","title":"DHCP协议","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":20,"slug":"dhcp协议","description":"DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,统一使用两个IANA分配的端口:67(服务器端),68(客户端),DHCP客户端使用的源端口号为68,目的端口号为67发送请求消息到...","relativePath":"Tech/Network/应用层/DHCP协议.md","rawContent":"DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,统一使用两个IANA分配的端口:67(服务器端),68(客户端),DHCP客户端使用的源端口号为68,目的端口号为67发送请求消息到DHCP服务器,DHCP服务器使用的源端口号为67,目的端口号为68回应应答消息给DHCP客户端。\r\n\r\nDHCP通常被用于局域网环境,主要作用是集中的管理、分配IP地址,使client动态的获得IP地址、子网掩码,网关地址、DNS服务器地址等信息,并能够提升地址的使用率。\r\n# 1. 报文格式\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411272200400.png)\r\n- **op**:报文的操作类型。分为请求报文和响应报文。1:请求报文,2:应答报文。即client送给server的封包,设为1,反之为2。\r\n - 请求报文:DHCP Discover、DHCP Request、DHCP Release、DHCP Inform和DHCP Decline。\r\n - 应答报文:DHCP Offer、DHCP ACK和DHCP NAK。\r\n- **htype**:DHCP客户端的MAC地址类型。MAC地址类型其实是指明网络类型。htype值为1时表示为最常见的以太网MAC地址类型。\r\n- **hlen**:DHCP客户端的MAC地址长度。以太网MAC地址长度为6个字节,即以太网时hlen值为6。\r\n- **hops**:DHCP报文经过的DHCP中继的数目,默认为0。DHCP请求报文每经过一个DHCP中继,该字段就会增加1。没有经过DHCP中继时值为0。(若数据包需经过router传送,每站加1,若在同一网内,为0)\r\n- **xid**:客户端通过DHCP Discover报文发起一次IP地址请求时选择的随机数,相当于请求标识。用来标识一次IP地址请求过程。在一次请求中所有报文的Xid都是一样的。\r\n- **secs**:DHCP客户端从获取到IP地址或者续约过程开始到现在所消耗的时间,以秒为单位。在没有获得IP地址前该字段始终为0。(DHCP客户端开始DHCP请求后所经过的时间。目前尚未使用,固定为0。)\r\n- **flags**:标志位,只使用第0比特位,是广播应答标识位,用来标识DHCP服务器应答报文是采用单播还是广播发送,0表示采用单播发送方式,1表示采用广播发送方式。其余位尚未使用。(即从0-15bits,最左1bit为1时表示server将以广播方式传送封包给client。)\r\n - 注意:在客户端正式分配了IP地址之前的第一次IP地址请求过程中,所有DHCP报文都是以广播方式发送的,包括客户端发送的DHCP Discover和DHCP Request报文,以及DHCP服务器发送的DHCP Offer、DHCP ACK和DHCP NAK报文。当然,如果是由DHCP中继器转的报文,则都是以单播方式发送的。另外,IP地址续约、IP地址释放的相关报文都是采用单播方式进行发送的。\r\n- **ciaddr**:DHCP客户端的IP地址。仅在DHCP服务器发送的ACK报文中显示,因为在得到DHCP服务器确认前,DHCP客户端是还没有分配到IP地址的。在其他报文中均显示,只有客户端是Bound、Renew、Rebinding状态,并且能响应ARP请求时,才能被填充。\r\n- **yiaddr**:DHCP服务器分配给客户端的IP地址。仅在DHCP服务器发送的Offer和ACK报文中显示,其他报文中显示为0。\r\n- **siaddr**:为DHCP客户端分配IP地址等信息的DHCP服务器IP地址。仅在DHCP Offer、DHCP ACK报文中显示,其他报文中显示为0。(用于bootstrap过程中的IP地址)。一般来说是服务器的ip地址,当报文的源地址、siaddr、server_id字段不一致(有经过跨子网转发)时,通常认为srever_id字段为真正的服务器ip,siaddr有可能是多次路由跳转中的某一个路由的ip\r\n- **giaddr**:DHCP客户端发出请求报文后经过的第一个DHCP中继的IP地址。如果没有经过DHCP中继,则显示为0。(转发代理(网关)IP地址)\r\n- **chaddr**:DHCP客户端的MAC地址。在每个报文中都会显示对应DHCP客户端的MAC地址。\r\n- **sname**:为DHCP客户端分配IP地址的DHCP服务器名称(DNS[域名](https://dnspod.cloud.tencent.com/?from_column=20065&from=20065)格式)。在Offer和ACK报文中显示发送报文的DHCP服务器名称,其他报文显示为0。\r\n- **file**:DHCP服务器为DHCP客户端指定的启动配置文件名称及路径信息。仅在DHCP Offer报文中显示,其他报文中显示为空。\r\n- **options**:可选项字段,长度可变,格式为”代码+长度+数据”。\r\n## 1.1. option字段\r\nDHCP Options 的格式由 **Magic Cookie**和若干个TLV组成。\r\nMagic Cookie占据开头的四个字节,固定值为网络字节序十六进制数”63 82 53 63“,用于标识后续数据的解释模式。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250112215326267.png)\r\n- **Type(代码)**:占一个字节,取值范围是 0-255,用于指定选项的类型。\r\n- **Length(长度)**:占一个字节,单位是字节,表示 Value 字段的长度。\r\n- **Value(值)**:其长度由 Length 字段指定,用于存放具体的选项信息内容。\r\n举例:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250112214915985.png)\r\n- **选项代码(Type)**:第一个字节 “06”,在 DHCP 选项代码中,“06” 代表 “DNS Servers”(域名服务器)选项 。这表明该选项用于指定客户端应使用的DNS服务器地址。\r\n- **长度(Length)**:第二个字节 “08”,表示后面 “Value” 部分的长度为 8 字节。\r\n- **值(Value)**:\r\n - 接下来的 8 个字节 “64 64 02 88 64 64 02 8a” 是 DNS 服务器的地址信息。这里的地址表示形式是以网络字节序(大端序)存储的 IP 地址。每 4 个字节代表一个 IP 地址,所以这里包含两个 IP 地址。\r\n - 将 “64 64 02 88” 转换为十进制,通过计算可得为 “100.100.2.136”。\r\n - 将 “64 64 02 8a” 转换为十进制,可得为 “100.100.2.138”。\r\n所以,这个 DHCP Option 的含义是为客户端指定了两个 DNS 服务器地址,分别是 100.100.2.136 和 100.100.2.138 。\r\n# 2. DHCP报文种类\r\nDHCP使用 “Message Type” 选项来明确报文类型,该选项在 DHCP 报文中作为一个可变长度选项存在,代码为53 。DHCP一共有8种报文,各种类型报文的基本功能如下:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250112215500081.png)\r\n\r\n| 报文类型 | 说明 |\r\n| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\r\n| Discover(0x01) | DHCP客户端在请求IP地址时并不知道DHCP服务器的位置,因此DHCP客户端会在本地网络内以广播方式发送Discover请求报文,以发现网络中的DHCP服务器。所有收到Discover报文的DHCP服务器都会发送应答报文,DHCP客户端据此可以知道网络中存在的DHCP服务器的位置。 |\r\n| Offer(0x02) | DHCP服务器收到Discover报文后,就会在所配置的地址池中查找一个合适的IP地址,加上相应的**租约期限**和其他配置信息(如网关、DNS服务器等),构造一个Offer报文,发送给DHCP客户端,告知用户本服务器可以为其提供IP地址。但这个报文只是告诉DHCP客户端可以提供IP地址,最终还需要客户端通过ARP来检测该IP地址是否重复。 |\r\n| Request(0x03) | DHCP客户端可能会收到很多Offer请求报文,所以必须在这些应答中选择一个。通常是选择第一个Offer应答报文的服务器作为自己的目标服务器,并向该服务器发送一个广播的Request请求报文,通告选择的服务器,希望获得所分配的IP地址。另外,DHCP客户端在成功获取IP地址后,在地址使用租期达到50%时,会向DHCP服务器发送单播Request请求报文请求续延租约,如果没有收到ACK报文,在租期达到87.5%时,会再次发送广播的Request请求报文以请求续延租约。 |\r\n| ACK(0x05) | DHCP服务器收到Request请求报文后,根据Request报文中携带的用户MAC来查找有没有相应的租约记录,如果有则发送ACK应答报文,通知用户可以使用分配的IP地址。 |\r\n| NAK(0x06) | 如果DHCP服务器收到Request请求报文后,没有发现有相应的租约记录或者由于某些原因无法正常分配IP地址,则向DHCP客户端发送NAK应答报文,通知用户无法分配合适的IP地址。 |\r\n| Release(0x07) | 当DHCP客户端不再需要使用分配IP地址时(一般出现在客户端关机、下线等状况)就会主动向DHCP服务器发送RELEASE请求报文,告知服务器用户不再需要分配IP地址,请求DHCP服务器释放对应的IP地址。 |\r\n| Decline(0x04) | DHCP客户端收到DHCP服务器ACK应答报文后,通过地址冲突检测发现服务器分配的地址冲突或者由于其他原因导致不能使用,则会向DHCP服务器发送Decline请求报文,通知服务器所分配的IP地址不可用,以期获得新的IP地址。 |\r\n| Inform(0x08) | DHCP客户端如果需要从DHCP服务器端获取更为详细的配置信息,则向DHCP服务器发送Inform请求报文;DHCP服务器在收到该报文后,将根据租约进行查找到相应的配置信息后,向DHCP客户端发送ACK应答报文。目前基本上不用了。 |\r\n\r\n# 3. DHCP Discover\r\n只有跟DHCP客户端在同一个网段的DHCP服务器才能收到DHCP客户端广播的DHCP DISCOVER报文。当DHCP客户端与DHCP服务器不在同一个网段时,必须部署DHCP中继来转发DHCP客户端和DHCP服务器之间的DHCP报文。在DHCP客户端看来,DHCP中继就像DHCP服务器;在DHCP服务器看来,DHCP中继就像DHCP客户端。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250112210118546.png)\r\n第一步:发现阶段\r\n首次接入网络的DHCP客户端不知道DHCP服务器的[IP地址](https://info.support.huawei.com/info-finder/encyclopedia/zh/IPv4.html \"IPv4\"),为了学习到DHCP服务器的IP地址,DHCP客户端以广播方式发送DHCP DISCOVER报文(目的IP地址为255.255.255.255)给同一网段内的所有设备(包括DHCP服务器或中继)。DHCP DISCOVER报文中携带了客户端的MAC地址([chaddr字段](https://support.huawei.com/hedex/pages/EDOC1100087046AZJ0324D/10/EDOC1100087046AZJ0324D/10/resources/dc/dc_cfg_dhcp_6005.html#ZH-CN_CONCEPT_0176371535__c1))、需要请求的参数列表选项([Option55](https://support.huawei.com/hedex/pages/EDOC1100087046AZJ0324D/10/EDOC1100087046AZJ0324D/10/resources/dc/dc_cfg_dhcp_6005.html#ZH-CN_CONCEPT_0176371535__op55))、广播标志位([flags字段](https://support.huawei.com/hedex/pages/EDOC1100087046AZJ0324D/10/EDOC1100087046AZJ0324D/10/resources/dc/dc_cfg_dhcp_6005.html#ZH-CN_CONCEPT_0176371535__f1))等信息。\r\n\r\n第二步:提供阶段\r\n与DHCP客户端位于同一网段的DHCP服务器都会接收到DHCP DISCOVER报文,DHCP服务器选择跟接收DHCP DISCOVER报文接口的IP地址处于同一网段的地址池,并且从中选择一个可用的IP地址,然后通过DHCP OFFER报文发送给DHCP客户端。\r\n\r\n通常,DHCP服务器的地址池中会指定IP地址的[租期](https://support.huawei.com/hedex/hdx.do?docid=EDOC1100087046&id=ZH-CN_CONCEPT_0176371534&lang=zh),如果DHCP客户端发送的DHCP DISCOVER报文中携带了期望租期,服务器会将客户端请求的期望租期与其指定的租期进行比较,选择其中时间较短的租期分配给客户端。\r\n\r\nDHCP服务器在地址池中为客户端分配IP地址的顺序如下:\r\n1. DHCP服务器上已配置的与客户端MAC地址静态绑定的IP地址。\r\n2. 客户端发送的DHCP DISCOVER报文中[Option50](https://support.huawei.com/hedex/pages/EDOC1100087046AZJ0324D/10/EDOC1100087046AZJ0324D/10/resources/dc/dc_cfg_dhcp_6005.html#ZH-CN_CONCEPT_0176371535__op50)(请求IP地址选项)指定的地址。\r\n3. 地址池内查找“Expired”状态的IP地址,即曾经分配给客户端的超过租期的IP地址。\r\n4. 在地址池内随机查找一个“Idle”状态的IP地址。\r\n5. 如果未找到可供分配的IP地址,则地址池依次自动回收超过租期的(“Expired”状态)和处于冲突状态(“Conflict”状态)的IP地址。回收后如果找到可用的IP地址,则进行分配;否则,DHCP客户端等待应答超时后,重新发送DHCP DISCOVER报文来申请IP地址。\r\n\r\n设备支持在地址池中排除某些不能通过DHCP机制进行分配的IP地址。例如,客户端所在网段已经手工配置了地址为192.168.1.100/24的DNS服务器,DHCP服务器上配置的网段为192.168.1.0/24的地址池中需要将192.168.1.100的IP地址排除,不能通过DHCP分配此地址,否则,会造成地址冲突。\r\n\r\n为了防止分配出去的IP地址跟网络中其他客户端的IP地址冲突,DHCP服务器在发送DHCP OFFER报文前通过发送源地址为DHCP服务器IP地址、目的地址为预分配出去IP地址的[ICMP](https://info.support.huawei.com/info-finder/encyclopedia/zh/ICMP.html \"ICMP\") ECHO REQUEST报文对分配的IP地址进行地址冲突探测。如果在指定的时间内没有收到应答报文,表示网络中没有客户端使用这个IP地址,可以分配给客户端;如果指定时间内收到应答报文,表示网络中已经存在使用此IP地址的客户端,则把此地址列为冲突地址,然后等待重新接收到DHCP DISCOVER报文后按照前面介绍的选择IP地址的优先顺序重新选择可用的IP地址。\r\n\r\n此阶段DHCP服务器分配给客户端的IP地址不一定是最终确定使用的IP地址,因为DHCP OFFER报文发送给客户端等待16秒后如果没有收到客户端的响应,此地址就可以继续分配给其他客户端。通过下面的选择阶段和确认阶段后才能最终确定客户端可以使用的IP地址。\r\n\r\n第三步:选择阶段\r\n\r\n如果有多个DHCP服务器向DHCP客户端回应DHCP OFFER报文,则DHCP客户端一般只接收第一个收到的DHCP OFFER报文,然后以广播方式发送DHCP REQUEST报文,该报文中包含客户端想选择的DHCP服务器标识符(即[Option54](https://support.huawei.com/hedex/pages/EDOC1100087046AZJ0324D/10/EDOC1100087046AZJ0324D/10/resources/dc/dc_cfg_dhcp_6005.html#ZH-CN_CONCEPT_0176371535__op54))和客户端IP地址(即[Option50](https://support.huawei.com/hedex/pages/EDOC1100087046AZJ0324D/10/EDOC1100087046AZJ0324D/10/resources/dc/dc_cfg_dhcp_6005.html#ZH-CN_CONCEPT_0176371535__op50),填充了接收的DHCP O\r\n'\r\n\r\nFFER报文中yiaddr字段的IP地址)。\r\n\r\nDHCP客户端广播发送DHCP REQUEST报文通知所有的DHCP服务器,它将选择某个DHCP服务器提供的IP地址,其他DHCP服务器可以重新将曾经分配给客户端的IP地址分配给其他客户端。\r\n\r\n第四步:确认阶段\r\n\r\n当DHCP服务器收到DHCP客户端发送的DHCP REQUEST报文后,DHCP服务器回应DHCP ACK报文,表示DHCP REQUEST报文中请求的IP地址([Option50](https://support.huawei.com/hedex/pages/EDOC1100087046AZJ0324D/10/EDOC1100087046AZJ0324D/10/resources/dc/dc_cfg_dhcp_6005.html#ZH-CN_CONCEPT_0176371535__op50)填充的)分配给客户端使用。\r\n\r\nDHCP客户端收到DHCP ACK报文,会广播发送[免费ARP](https://info.support.huawei.com/info-finder/encyclopedia/zh/ARP.html \"ARP\")报文,探测本网段是否有其他终端使用服务器分配的IP地址,如果在指定时间内没有收到回应,表示客户端可以使用此地址。如果收到了回应,说明有其他终端使用了此地址,客户端会向服务器发送DHCP DECLINE报文,并重新向服务器请求IP地址,同时,服务器会将此地址列为冲突地址。当服务器没有空闲地址可分配时,再选择冲突地址进行分配,尽量减少分配出去的地址冲突。\r\n\r\n当DHCP服务器收到DHCP客户端发送的DHCP REQUEST报文后,如果DHCP服务器由于某些原因(例如协商出错或者由于发送REQUEST过慢导致服务器已经把此地址分配给其他客户端)无法分配DHCP REQUEST报文中[Option50](https://support.huawei.com/hedex/pages/EDOC1100087046AZJ0324D/10/EDOC1100087046AZJ0324D/10/resources/dc/dc_cfg_dhcp_6005.html#ZH-CN_CONCEPT_0176371535__op50)填充的IP地址,则发送DHCP NAK报文作为应答,通知DHCP客户端无法分配此IP地址。DHCP客户端需要重新发送DHCP DISCOVER报文来申请新的IP地址。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250112214318695.png)\r\n![[dhcp_discover.pcap]]\r\n# 4. DHCP续约\r\n时间点:当续约周期剩余一半时会尝试首次续约;续约失败,在剩余八分之一时会尝试再次续约;如果仍然失败,则到期后会释放。\r\n机制:客户端发起DHCP Request,服务端发送DHCP ACK\r\n# 5. 抓包分析\r\n\r\n```shell\r\ntcpdump udp port 67 or udp port 68 or arp\r\n```\r\n192.168.85.100通过DHCP获取到IP后,会发送ARP探测报文检查IP是否冲突,然后发送ARP广播报文。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411272301653.png)\r\n![[Tech/Cloud Network/dhcp.pcapng]]\r\n\r\n## 5.1. Offer报文\r\n提供了ip地址、dns server(option 6)、租期(option 51)\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411272310120.png)\r\n\r\n```shell\r\n╰─$ scutil --dns\r\nDNS configuration\r\n\r\nresolver #1\r\n nameserver[0] : 202.106.46.151\r\n nameserver[1] : 202.106.195.68\r\n flags : Request A records\r\n reach : 0x00000002 (Reachable)\r\n```\r\n# 6. Reference\r\n[DHCP协议详解-腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/2081483)\r\nhttps://info.support.huawei.com/info-finder/encyclopedia/zh/DHCP.html"},{"id":"dns zone","title":"Dns Zone","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":8,"slug":"dns-zone","description":"DNS Zone是指域名系统(DNS)中管理的一部分域空间,包含了该区域内所有域名的记录信息。它是确保网络中计算机能够通过域名解析到正确 IP 地址的关键组成部分,对于维持互联网的正常运行至关重要。一个DNS Zone通常由一个组织或个人负责维护,这个区域包括了域名及其子域的权威信息,如IP地址、邮...","relativePath":"Tech/Network/应用层/DNS/dns zone.md","rawContent":"DNS Zone是指域名系统(DNS)中管理的**一部分域空间**,包含了该区域内所有域名的记录信息。它是确保网络中计算机能够通过域名解析到正确 IP 地址的关键组成部分,对于维持互联网的正常运行至关重要。一个DNS Zone通常由一个组织或个人负责维护,这个区域包括了域名及其子域的权威信息,如IP地址、邮件交换记录等。\r\n\r\n|区域类型|功能|数据权限|使用场景|\r\n|---|---|---|---|\r\n|主要区域|维护和管理完整的DNS记录|读写|主DNS服务器用于直接管理域名解析数据|\r\n|辅助区域|主区域的只读副本|只读|用于冗余、负载均衡和容灾|\r\n|存根区域|存储目标区域的NS记录|部分信息|用于加速DNS查询和优化跨区域解析|\r\n# 1. 结构组成\r\n**域名称空间分层**: DNS被分解成多个不同的zone。这些区域区分了DNS命名空间中不同管理的区域。DNS区域是由特定组织或管理员管理的DNS命名空间的一部分。DNS区域是一个管理空间,允许对DNS组件(如权威域名服务器)进行更精细的控制。域名空间是一个层次树状结构,DNS根域位于树的顶端。一个DNS区域从树中的某个域开始,**并可以延伸到所有子域**,每个节点代表一个域名的权威区域/使得多个子域可以由同一个实体管理。\r\n\r\n**权威名称服务器**: 对于每个DNS Zone, 至少有一个[[应用层DNS协议解析#2.1.3. 权威域名服务器|权威名称服务器]]负责解析属于该区域的查询请求,返回相应的IP地址或其他资源记录。\r\n\r\n# 2. DNS zone与subzone\r\n一个常见的误解是将DNS区域等同于域名或单个DNS服务器。事实上,一个DNS区域可以**包含多个子域**,并且多个区域可以存在于同一台服务器上。DNS区域**并非**物理上相互分离,区域严格用于**委派控制**。\r\n\r\n## 2.1. 例一\r\n在baidu中设置一个DNS服务器,这个DNS服务器将完成域名空间\"baidu.com\"下的域名解析工作,我们称之为一个区域(ZONE)。\r\n在huawei中设置一个DNS服务器,这个服务器完成域名空间\"huawei.com\"下的域名解析工作,我们称之为一个区域(ZONE)。\r\n> [!note]\r\n> 在DNS服务器中,必须先建立区域,在区域中建立子域,在区域或者子域中添加主机记录。一个zone的所有信息被存储在DNS zone文件中,这个文件是理解一个DNS zone如何工作的关键。一台DNS服务器上可以存放多个区域文件,同一个区域文件也可以存放在多台DNS服务器上。\r\n## 2.2. 例二 \r\n假设有一个cloudflare.com域,包括三个子域:support.cloudflare.com、community.cloudflare.com和blog.cloudflare.com。假设博客是一个需要独立管理的强大站点,而支持和社区页面与cloudflare.com更紧密相关,可以与主域在同一个区域中管理。在这种情况下,cloudflare.com以及支持和社区站点将全部位于同一个区域,而blog.cloudflare.com将存在于其自身的区域中。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250328223840.png)\r\n\r\n# 3. DNS zone分类\r\n## 3.1. 正向与反向DNS Zone:\r\n- 正向DNS Zone通过域名来解析IP地址\r\n- 反向DNS Zone则是通过IP地址来解析域名,适用于不同的查询需求。\r\n## 3.2. 主从DNS Zone\r\n- 主DNS Zone是读写的原版区域\r\n- 从DNS Zone仅可读并从主区域同步数据,用于负载均衡和容错。\r\n## 3.3. public与private zone\r\n### 3.3.1. public zone\r\n用于管理和解析公网域名。公网域名是指在Internet中,使用公网解析的域名,用于访问网站或Web应用程序,需要通过域名注册商注册。\r\n### 3.3.2. private zone\r\n用于管理和解析内网域名。内网域名是指在VPC中生效的域名。云解析服务将内网域名与私网IP地址相关联,为云上云服务提供**VPC内**的域名解析服务。\r\n\r\n内网域名具有以下特点:\r\n- 可以随意创建,无需进行注册、域名实名认证以及备案。\r\n- 仅在关联VPC内生效,一个域名可以关联多个VPC,且数量没有限制。\r\n\r\n启该子域名递归解析代理功能后,解析的子域名未在内网域名记录集中配置时,支持使用公网域名递归解析。\r\n\r\n不同的VPC(虚拟私有云)可以配置不同的DNS区域(DNS Zone),并且在这些不同的DNS区域中,对于相同的域名可以设置不同的域名解析记录。\r\n#### 3.3.2.1. 多环境部署\r\n在不同的VPC中,比如开发、测试和生产环境,可以为同一个域名配置不同的解析地址。例如:\r\n- 开发环境的DNS Zone可能将`app.example.com`解析到开发服务器IP\r\n- 生产环境的DNS Zone将同一个域名解析到生产服务器IP\r\n#### 3.3.2.2. 多区域/多地域部署 \r\n对于跨地域的服务,可以根据不同VPC的地理位置配置就近的服务器解析:\r\n- 中国区的VPC解析到中国的服务器\r\n- 美国区的VPC解析到美国的服务器\r\n#### 3.3.2.3. 灾备和负载均衡\r\n可以在不同的DNS Zone中配置不同的解析策略,实现流量分配和故障切换。\r\n# 4. ref\r\n[What is a dns zone?](https://www.cloudflare.com/learning/dns/glossary/dns-zone/)"},{"id":"DNS","title":"DNS","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"dns","description":"","relativePath":"Tech/Network/应用层/DNS/DNS.md","rawContent":""},{"id":"DNS配置与匹配规则","title":"DNS配置与匹配规则","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"dns配置与匹配规则","description":"1. multi dns ip 对于一个主机配置多个DNS IP 1.1. 作用 1.1.1. 冗余和容错 - 当第一个DNS服务器无法响应时,主机会自动尝试使用第二个、第三个DNS IP - 提高DNS解析的可靠性,即使一个DNS服务器宕机,仍可通过其他DNS服务器进行域名解析 1.1.2. 负载...","relativePath":"Tech/Network/应用层/DNS/DNS配置与匹配规则.md","rawContent":"# 1. multi dns ip\r\n对于一个主机配置多个DNS IP\r\n## 1.1. 作用\r\n### 1.1.1. 冗余和容错\r\n- 当第一个DNS服务器无法响应时,主机会自动尝试使用第二个、第三个DNS IP\r\n- 提高DNS解析的可靠性,即使一个DNS服务器宕机,仍可通过其他DNS服务器进行域名解析\r\n### 1.1.2. 负载均衡\r\n- 多个DNS IP可以分散解析请求\r\n- 减轻单个DNS服务器的负载压力\r\n## 1.2. 协调机制\r\n- 默认按照配置顺序轮询,解析过程是串行的,而非并行\r\n- 第一个DNS IP优先使用,如果第一个DNS服务器在一定时间内(通常是几秒钟)未响应,则会尝试下一个DNS IP\r\n\r\n域名服务器响应存在以下可能\r\n- 域名不存在(NXDOMAIN):通常会立即返回错误\r\n- 服务器暂时不可用(SERVFAIL):会触发切换到备用DNS\r\n> [!note]\r\n> 在NXDOMAIN(Non-Existent Domain)的情况下,不会再通过下一个DNS服务器查询。XDOMAIN表域名definitively不存在,这是一个权威的否定响应,意味着在权威DNS服务器上确认该域名不存在。NXDOMAIN是一个确定性的响应,更换DNS服务器不会改变域名不存在的事实。\r\n# 2. longest_suffix与wildcard \r\n## 2.1. 标准域名分级匹配wildcard\r\n- 基于域名的层次结构,从右到左逐级匹配\r\n- 严格按照域名注册体系的规则进行匹配\r\n- 以\".\"作为分隔符,按照标准TLD(顶级域名)、SLD(二级域名)等层级划分\r\n- 例如:对于 example.com,先匹配 com(TLD),再匹配 example(SLD)\r\n## 2.2. 最长尾缀匹配\r\n- 优先匹配最具体、最长的尾缀部分\r\n- 主要用于公共后缀列表(PSL)如Mozilla维护的列表中\r\n- 能够有效处理特殊情况,如 co.uk、github.io 等公共后缀\r\n- 例如:对于 blog.github.io,传统方法会将io视为TLD,而最长尾缀匹配会将整个 github.io 视为有效公共后缀\r\n## 2.3. 实际应用区别\r\n- 在处理 example.co.uk 时:\r\n - 标准匹配会将 uk 视为TLD,co 为SLD\r\n - 最长尾缀匹配会将 co.uk 整体视为有效公共后缀\r\n\r\n最长尾缀匹配在处理现代域名系统中的复杂情况(如国家代码二级域名、新通用顶级域名等)时更为灵活有效,特别是在确定有效注册域名和Cookie域范围时具有明显优势。"},{"id":"https抓包","title":"Https抓包","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"https抓包","description":"由于主流浏览器都只支持HTTP/2 Over TLS,也就是说当前HTTP/2网站都使用了HTTPS,数据传输都经过了SSL加密,常规抓包方法并不能看到明文数据。 1. wireshark Wireshark 的抓包原理是直接读取并分析网卡数据,要想让它解密 HTTPS 流量,有两个办法: 1)如果...","relativePath":"Tech/Network/应用层/https抓包.md","rawContent":"由于主流浏览器都只支持HTTP/2 Over TLS,也就是说当前HTTP/2网站都使用了HTTPS,数据传输都经过了SSL加密,常规抓包方法并不能看到明文数据。\n# 1. wireshark\nWireshark 的抓包原理是直接读取并分析网卡数据,要想让它解密 HTTPS 流量,有两个办法: \n1)如果你拥有 HTTPS 网站的加密私钥,可以用来解密这个网站的加密流量; \n2)某些浏览器支持将 TLS 会话中使用的对称密钥保存在外部文件中,可供 Wireshark 加密使用。\n\nFirefox 和 Chrome 都支持生成上述第二种方式的文件,具体格式见这里:[NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)。 \n但 Firefox 和 Chrome 只会在系统环境变量中存在 SSLKEYLOGFILE 路径时才会生成它,先来加上这个环境变量(以 OSX 为例):\n```shell\n# 新建sslkeylog.log文件\nmkdir ~/tls && touch ~/tls/sslkeylog.log\n# 添加环境变量\nexport SSLKEYLOGFILE=~/tls/sslkeylog.log\n```\n接着,在 Wireshark选项中按如下设置TLS\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241227214238173.png)\n接着控制台打开chrome\n```\nopen /Applications/Google\\ Chrome.app\n```\n## 1.1. windows设置\n```bash\n--ssl-key-log-file=D:\\sslkey.log\n复制代码\n```\n在windows系统下可以右键点击Chrome浏览器的快捷方式进行设置:\n\n![image-20220315102335795](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c03a5f7c5ba24bc7a410d357df2500f3~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)\n\n"},{"id":"HTTP协议解析","title":"HTTP协议解析","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":25,"slug":"http协议解析","description":"1. HTTP 1.0 !http1.0抓包.pcapng HTTP/1.0 默认为每一对 HTTP 请求/响应都打开一个单独的 TCP 连接。 - 17945-17947:tcp三次握手 - 17948:Server告诉Client更新自己的接收窗口大小 - 17949:Client发起HTTP ...","relativePath":"Tech/Network/应用层/HTTP协议解析.md","rawContent":"# 1. HTTP 1.0\r\n![[http1.0抓包.pcapng]]\r\nHTTP/1.0 默认为每一对 HTTP 请求/响应都打开一个单独的 TCP 连接。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241218203403332.png)\r\n\r\n- 17945-17947:tcp三次握手\r\n- 17948:Server告诉Client更新自己的接收窗口大小\r\n- 17949:Client发起HTTP Request\r\n- 17950: Server对Client Request报文的ACK\r\n- 17951: `[PSH]`标志表示server请求立即推送数据,即server希望client方尽快将数据递交给应用层处理\r\n- 17952: client对17950-17951的ack\r\n- 17953: server响应HTTP Response\r\n- 17954:Client对Server响应的ACK\r\n- 17955-17958: tcp四次挥手\r\n## 1.1. 17949-HTTP Request\r\n一个HTTP Request包括请求行(Request Line)、请求头部(Request Headers)以及请求正文(Request Body,可选),三部分之间通过CRLF(0x0d0a)分割。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241218210500406.png)\r\n\r\n### 1.1.1. 请求行\r\nMethod、URI、HTTP-Version,三部分之间通过空格(0x20)分割\r\n### 1.1.2. 请求头\r\n多个`Header-Name:Header-Value`,key和value通过冒号(0x3a)分割,多个请求头之间通过CRLF(0x0d0a)分割\r\n### 1.1.3. 请求体(可选)\r\n一个请求体为hello的post请求\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241218212919400.png)\r\n\r\n## 1.2. 17953-HTTP Response\r\n一个HTTP Response包括状态行(Response Line)、响应头部(Response Headers)以及响应正文(Reponse Body,可选),三部分之间通过CRLF(0x0d0a)分割。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241218211135818.png)\r\n\r\n### 1.2.1. 状态行\r\nHTTP-Version、Status、Code Reason,三部分用空格(0x20)分割\r\nStatus: 三位数字的http状态码\r\nPhrase:0x4f4b,在ASCII码表表示“ok”,对响应的简短描述\r\n### 1.2.2. 响应头部\r\n多个`Header-Name:Header-Value`,key和value通过冒号(0x3a)分割,多个请求头之间通过CRLF(0x0d0a)分割\r\n### 1.2.3. 响应正文(可选)\r\n# 2. http/1.1\r\n相比于http1.0,http1.1有以下改进。\r\n## 2.1. [[http连接池#1.4. http1.1长连接测试|长连接]]\r\nHTTP/1.1默认开启了长连接,避免了每次客户端与服务器请求都要重复建立释放TCP连接,提高了网络的利用率。如果客户端想关闭HTTP连接,可以在请求头中携带`Connection:false`来告知服务器关闭请求\r\n\r\n## 2.2. 支持请求管道化(pipelining)\r\n基于HTTP/1.1的长连接,使得请求管道化成为可能。管线化使得请求能够“并行”传输。举个例子来说,假如响应的主体是一个html页面,页面中包含了很多img,能够进行“并行”发送多个请求。注意这里的“并行”并不是真正意义上的并行传输,服务器必须按照客户端请求的先后顺序依次回送相应的结果,以保证客户端能够区分出每次请求的响应内容。也就是说,HTTP管道化的请求与响应其实是一个先进先出的队列。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241220224820551.png)\r\n\r\n由于管道化请求和响应是一个先进先出的队列,因此仍然存在队头阻塞问题。目前大多数服务器和浏览器都不支持管道化能力,因此pipeline并不是一个被广泛使用的能力。\r\n## 2.3. cache-control\r\n在 HTTP/1.1 中,`cache-control`是一个非常重要的头字段,用于控制缓存机制。它允许服务器和客户端在请求和响应中传递有关缓存的指令,这些指令决定了浏览器和中间缓存服务器(如代理服务器)如何缓存和重用资源。通过合理使用`Cache-Control`,可以减少网络流量、提高性能和减轻服务器负载。`Cache-Control`最常用于管理静态资源缓存,这个报头独立应用于每一个资源,这意味着我们页面中的一切都可以拥有一个非常定制化、颗粒化的缓存政策。我们由此可以得到大量的控制权,得以制定异常复杂而强大的缓存策略。\r\n\r\n一个典型的cache-control报文可能如下:\r\n```\r\nache-Control: public, max-age=31536000\r\n```\r\n`Cache-Control`就是报头字段名,`public`和`max-age=31536000`是**指令**。\r\n\r\n### 2.3.1. `public`和`private`\r\n`public` 意味着包括CDN、代理服务器之类的任何缓存都可以存储响应的副本。`public` 指令经常是冗余的,因为其他指令的存在(例如 `max-age`)已经隐式表示响应是可以缓存的。\r\n相比之下,`private` 是一个显式指令,表示只有响应的最终接收方(客户端或浏览器)可以缓存文件。虽然 `private`本身并不具备安全功能,但它意在有效防止公共缓存(如 cdn)存储包含用户个人信息的响应(所谓君子协定)。\r\n### 2.3.2. max-age和s-maxage\r\n`max-age` 定义了一个确保响应被视为“新鲜”的时间单位(相对于请求时间,以秒计)。`Cache-Control: max-age=60`代表可在接下来的60秒缓存和重用响应。这个 `Cache-Control` 报头告诉浏览器可以在接下来的 60 秒内从缓存中使用这个文件而不必担心是否需要重新验证。60 秒后,浏览器将回访服务器以重新验证该文件。如果有了一个新文件供浏览器下载,服务器会返回 `200`,浏览器下载新文件,旧文件也会从 HTTP 缓存中被剔除,新的文件会接替它,并应用新缓存报头。如果并没有新的副本供下载,服务器会返回 `304`,不需要下载新文件,使用新的报头来更新缓存副本。也就是说如果 `Cache-Control: max-age=60` 报头依然存在,缓存文件的 60 秒会重新开始。这个文件的总缓存时间是 120 秒。\r\n\r\n> **注意:**`max-age` 本身有一个巨坑,它告诉浏览器相关资源已经过期,但没有告诉这个过期版本绝对不能使用。浏览器可能使用它自己的机制来决定是否在不经验证的情况下释放文件的过期副本。这种行为有些不确定性,想确切知道浏览器会怎么做有点困难。\r\n\r\n`s-maxage`(注意 `max` 和 `age` 之间没有 `-`)会覆盖 `max-age` 指令,但只在公共缓存中生效。`max-age` 和 `s-maxage` 结合使用可以让你针对私有缓存和公共缓存(例如代理、CDN)分别设定不同的刷新时间。\r\n\r\n### 2.3.3. 其他类型\r\ncache-control还有很多奇多可选值,这里不一一赘述。\r\n## 2.4. 强制请求头添加host字段\r\n与http1.0相比,http1.1的请求头中新增了host字段,指定请求服务器的域名/IP地址和端口号。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241220230727287.png)\r\nhost请求头是必须要携带的,有以下几点原因。\r\n- 多应用部署,一台物理主机上可能通过tomcat等容器部署了多个应用服务,他们有不同的域名,在TCP层面他们的IP和端口号都是一样的,如果不添加host就无法区分访问的到底是哪个服务。\r\n- LB技术,现在的服务器端大都使用7层LB技术进行负载均衡,一个LB可能会为大量的应用提供负载均衡服务,通过这些应用都会具有不同的域名,通过CNAME或者A记录的方式最终到指向了相同的LB节点,如果不添加host,就无法区分被访问服务。\r\n## 2.5. 部分场景使用chunked transfer优化content-length\r\n对于GET等没有请求体的请求来说,可以通过连续的两个`\\r\\n`来识别到客户端请求已经完整接收了。对于POST等有请求体的请求来说,则必须在请求头中添加`content-length`来说明请求体的大小,否则服务端无法知道什么时候请求体能够接收完整(不能通过\\r\\n识别,因为请求体中有可能携带这样的内容)。如果客户端请求携带了请求体,而又没携带content-length的话,那么大多数服务器都会返回400 Bad Request或者陷入无限等待直到连接超时。这点在http1.0和http1.1上都是一样的。对于响应体来说,非长连接的请求可以通过连接的关闭来确定响应已经完整接收。但是对于长连接来说,则无法确认响应体是否已经发送完整,必须通过`content-length`来确认。\r\n\r\n通常,HTTP应答消息中发送的数据是整个发送的,`Content-Length`消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及后续应答消息的开始。然而,使用分块传输编码(`Transfer-Encoding: chunked`),数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。分块传输编码有以下好处:\r\n1. **分块传输**:允许服务器逐步生成和发送响应数据,而无需等待整个响应生成完成。这对于大文件或需要长时间计算的响应非常有用,因为客户端可以边接收数据边处理它。\r\n2. **降低内存开销**:对于大型响应,使用分块传输编码可以降低服务器和客户端的内存开销,因为它们不需要同时存储整个响应。\r\n3. **实时数据传输**:允许服务器实时传输数据,而无需等待整个数据生成。这在一些实时应用程序中非常有用,如聊天应用或实时游戏。\r\n4. **减少延迟**:分块传输可以减少客户端首次接收到数据的等待时间,因为服务器可以立即发送可用的数据块。\r\n5. **容错性**:如果连接意外断开,客户端仍然可以处理已接收的chunk,而无需丢弃整个响应。\r\n\r\n分块传输时,请求体被分成多个块,每个块有自己的大小标识。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF,也不包括分块数据结尾的 CRLF。最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。例如,一个分块传输的请求体可能像这样:\r\n- 块1大小(十六进制表示) + `\\r\\n` + 块 1 内容 + `\\r\\n` \r\n- 块 2 大小 + `\\r\\n` + 块2内容 + `\\r\\n`\r\n- `0\\r\\n`(最后一个块大小为 0,表示结束)\r\n通过这种方式,服务器可以逐块读取并处理请求体,而不需要预先知道整个请求体的长度。\r\n\r\n# 3. http/2\r\nhttp1.1的问题\r\n- HTTP/1.1客户端需要使用多个连接才能实现并发和缩短延迟\r\n- HTTP/1.1不会压缩请求头字段和响应头字段,从而产生不必要的网络流量\r\n- HTTP/1.1不支持有效的资源优先级,致使底层TCP连接的利用率低下。\r\n\r\nHTTP/2没有改动HTTP的应用语义,仍然使用HTTP的请求方法、状态码和头字段等规则。它主要修改了HTTP的报文传输格式,通过引入[二进制分帧](https://info.support.huawei.com/info-finder/encyclopedia/zh/HTTP--2.html#section10316131182915)层实现性能的提升。HTTP/2通过多路复用、头信息压缩、二进制分帧和服务器推送等特性,显著提高了性能,在网络条件相同的情况下,能够更快地加载网页,减少用户等待时间,提升用户体验。\r\n\r\n目前主流的平台均已支持了HTTP2协议,如图是baidu的请求页面数据\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241223223909543.png)\r\n\r\n## 3.1. 数据传输方式\r\n- **HTTP/1.1**\r\n - **文本格式**:HTTP/1.1 的请求和响应数据以文本格式传输,这种格式虽然可读性强,但解析和处理效率相对较低。例如,在处理大量数据时,文本格式的解析需要更多的CPU资源和时间。\r\n- **HTTP/2**\r\n - **二进制分帧**:HTTP/2 将数据分解为更小的帧,并以二进制格式进行传输。二进制格式比文本格式更紧凑,解析速度更快,能够更有效地利用网络带宽。帧是 HTTP/2 中最小的传输单位,包括头部帧、数据帧等不同类型,通过不同的帧类型和帧头标识来区分和处理不同的内容。\r\n### 3.1.1. 帧结构\r\nHTTP/2 连接的初始帧,也就是 “连接序言”(Connection Preface)帧用于标识HTTP/2连接的开始,其格式是固定的 `PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n`\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241227220022616.png)\r\nHTTP/2的数据帧结构主要由帧头和帧主体两部分构成,帧头为固定的9字节,变化的为帧的负载(Frame Payload),负载内容是由帧头中的帧类型(Type)定义。\r\n#### 3.1.1.1. 帧头\r\n所有的帧都包含一个 9 字节的帧头,其结构如下\r\n```\r\n+-----------------------------------------------+ \r\n| Length (24) | \r\n+---------------+---------------+---------------+ \r\n| Type (8) | Flags (8) | \r\n+-+-------------+---------------+-------------------------------+ \r\n|R| Stream Identifier (31) | +=+=============================================================+\r\n```\r\n- **Length**:无符号 24 位整型,表示帧的正文部分Payload的长度,最大值为即16384 字节。如果想要发送更大长度的帧,必须收到设置有`SETTINGS_MAX_FRAME_SIZE`的 SETTING帧 。需要注意的是,帧头的9字节不算在Length 的计算范围之内\r\n- **Type**:1 字节的帧类型标识,不同的帧类型具有不同的功能和用途,例如:\r\n - **0x0**:DATA 帧,主要用来传递消息体135.\r\n - **0x1**:HEADERS 帧,主要用于传递消息头135.\r\n - **0x2**:PRIORITY 帧,用于设置流的优先级125.\r\n - **0x3**:RST_STREAM 帧,用于终止异常流56.\r\n - **0x4**:SETTINGS 帧,用于设置连接配置参数56.\r\n - **0x5**:PUSH_PROMISE 帧,服务器推送之前告知客户端56.\r\n - **0x6**:PING 帧,发送端测量最小的 RTT 时间,检测连接是否可用56.\r\n - **0x7**:GOAWAY 帧,通知对端不要在连接上建新流,并可包含关闭连接的原因等信息56.\r\n - **0x8**:WINDOW_UPDATE 帧,用于实现流量控制56.\r\n - **0x9**:CONTINUATION 帧,用于延续一个报头区块56.\r\n- **Flags**:为帧类型保留的8位字段,有具体的布尔标识,不同帧类型的Flags有不同的含义,例如:\r\n - **END_STREAM(0x1)**:用于标识该帧是发送端对确定的流发送的最后一帧,设置此标志会导致流进入 “半关闭” 状态或者 “关闭” 状态136.\r\n - **END_HEADERS(0x4)**:位3表示帧包含了整个的报头块,且后面没有延续帧。不带有 END_HEADERS 标记的报头帧在同个流上后面必须跟着延续帧。接收端接收到任何其他类型的帧或者在其他流上的帧必须作为类型为协议错误的连接错误处理6.\r\n - **PADDED(0x8)**:表示是否有Padding\r\n - **PRIORITY(0x20)**:位6设置指示专用标记(E),流依赖及权重字段将会呈现6.\r\n- **R**:1位的保留字段,尚未定义语义,发送和接收时必须忽略,设置为 0x0\r\n- **Stream Identifier**:31 位无符号整型的流标识符,用于标识该帧属于哪个数据流。其中0x0作为保留值,表示与连接相关的帧作为一个整体而不是一个单独的流 。由客户端建立的 Stream ID 必须是奇数,由服务端建立的 Stream ID必须是偶数\r\n\r\n## 3.2. 连接管理——多路复用\r\nTCP连接会随着时间进行自我调节,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调节被称为TCP慢启动。这种调节让具有突发性和短时性的HTTP连接变的十分低效。HTTP/2通过多路复用让所有数据流使用同一个连接,有效使用TCP连接,让高带宽也能真正的服务于HTTP的性能提升,这是HTTP/2的核心特性之一。它允许在一个TCP连接上同时发送多个请求和响应,不同请求和响应之间不会相互阻塞。每个请求和响应都被分配一个唯一的流标识符(stream ID),数据以帧(frame)为单位进行传输,多个帧可以交错发送,然后在接收端根据流标识符重新组装。例如,在加载一个包含多个图片、脚本和样式表的网页时,所有资源的请求可以同时在一个连接上进行,大大提高了传输效率。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241223224526962.png)\r\n\r\n## 3.3. 头部信息压缩\r\nHTTP/1.1 的请求和响应头信息通常以明文形式传输,而且每次请求都要重复发送一些相同的头部字段(如`User-Agent`、`Host`等),这会增加不必要的传输开销。例如,对于一个包含多个资源请求的网页,每个请求都携带相同的`User-Agent`字段,浪费了带宽。\r\n\r\n**HPACK 压缩算法**:HTTP/2 采用HPACK算法对头信息进行压缩。它通过建立一个静态和动态的字典,将常用的头部字段及其值映射为索引,在传输时只发送索引值,从而大大减少了头信息的大小。同时,动态字典会根据传输的内容不断更新,提高压缩效率。例如,对于频繁出现的`User-Agent`字段,在首次传输时可能会发送完整内容,后续传输时则可以通过字典索引来表示,显著降低了头信息的传输量。-不同流的头部信息(Header)是相互独立的,当客户端发起多个请求时,每个请求在各自的流中传输,其头部信息只与该流相关。\r\n\r\n在 HTTP/2 中,头部信息主要通过 HEADERS 帧和 CONTINUATION 帧来传输。\r\n- **HEADERS 帧**:找到类型为`HEADERS`的帧。这些帧包含了请求或响应的头部信息。\r\n- **CONTINUATION 帧**:如果头部信息较长,无法在一个 HEADERS 帧中传输完,就会用到CONTINUATION帧来继续传输头部信息。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241227221018221.png)\r\n\r\n### 3.3.1.  帧头信息\r\n- **Type**: HEADERS (1)\r\n - 表明这是一个 HEADERS 帧,在 HTTP/2 中,HEADERS 帧用于传输请求或响应的头部信息。\r\n- **Flags**: 0x25\r\n - **Priority**: 该标志位被设置,表示此帧包含了优先级信息。\r\n - **End Headers**: 表示这是头部信息的最后一个帧。\r\n - **End Stream**: 表示此流的结束。\r\n- **Reserved**: 0x0\r\n - 保留位,未被使用。\r\n- **Stream Identifier**: 1\r\n - 标识这个帧所属的流 ID 为 1。\r\n### 3.3.2. 帧主体信息\r\n- **Pad Length**: 0\r\n - 表示没有填充(Padding)。\r\n- **Header Length**: 668\r\n - 头部信息块的长度为 668 字节。\r\n- **Header Block Fragment**\r\n - 这是头部信息压缩后的数据片段,这些数据是经过HPACK压缩算法处理后的头部信息。上图抓包结果中HEADER: method: GET后面是解压缩后的头。注意到,在HTTP/2中URL被编码在头部中的PATH字段中。\r\n- **动态表相关信息**\r\n - **Header Count**: 17\r\n - 表示头部字段的数量为 17 个。\r\n - **Weight**: 255 (Weight real: 256)\r\n - 流的权重为 255(实际权重为 256),用于优先级设置。\r\n## 3.4. 服务器推\r\n- **HTTP/1.1**\r\n - **无服务器推送**:在 HTTP/1.1 中,服务器只能被动地响应客户端的请求,无法主动向客户端推送数据。如果客户端需要获取多个资源,必须逐个发送请求。\r\n- **HTTP/2**\r\n - **支持服务器推送**:服务器可以主动向客户端推送资源,而无需客户端事先请求。例如,当客户端请求一个HTML页面时,服务器可以预见到客户端可能还需要加载相关的CSS和JavaScript文件,于是在响应HTML页面的同时,主动将这些资源推送给客户端,减少了客户端后续的请求次数,加快了页面的加载速度。\r\n# 4. http/3\r\nHTTP2协议虽然大幅提升了HTTP/1.1的性能,然而,基于TCP实现的HTTP2遗留下3个问题:\r\n- 有序字节流引出的**队头阻塞**([Head-of-line blocking](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Head-of-line_blocking)),使得HTTP2的多路复用能力大打折扣;\r\n- **TCP与TLS叠加了握手时延**,建链时长还有1倍的下降空间;\r\n- 基于TCP四元组确定一个连接,这种诞生于有线网络的设计,并不适合移动状态下的无线网络,这意味着**IP地址的频繁变动会导致TCP连接、TLS会话反复握手,**成本高昂。\r\n# 5. reference\r\nhttps://info.support.huawei.com/info-finder/encyclopedia/zh/HTTP--2.html\r\n[[http连接池]]\r\nhttps://www.cnblogs.com/caoweixiong/p/14720254.html\r\nhttps://juejin.cn/post/6844903935648497678\r\n[HTTP2中帧的定义](https://halfrost.com/http2-http-frames-definitions/)\r\n[wireshark抓包http2](https://www.cnblogs.com/jesse131/p/12686304.html)"},{"id":"TLS","title":"TLS","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":39,"slug":"tls","description":"1. TLS是什么 Transport Layer Security (TLS) 是一种被广泛采用的安全协议,旨在增强互联网通信的私密性和数据安全性。TLS的主要使用场景是对Web应用和服务器之间的通信(例如,Web 浏览器加载网站)进行加密。TLS 还可以用于加密其他通信,如电子邮件、消息传递和 ...","relativePath":"Tech/Network/应用层/TLS.md","rawContent":"# 1. TLS是什么\r\nTransport Layer Security (TLS) 是一种被广泛采用的安全协议,旨在增强互联网通信的私密性和数据安全性。TLS的主要使用场景是对Web应用和服务器之间的通信(例如,Web 浏览器加载网站)进行加密。TLS 还可以用于加密其他通信,如电子邮件、消息传递和 [IP 语音 (VoIP)](https://www.cloudflare.com/learning/video/what-is-voip/) 等。在本文中,我们将重点介绍 TLS 在 [Web 应用安全](https://www.cloudflare.com/learning/security/what-is-web-application-security/)中发挥的作用。\r\n\r\nTLS 由互联网工程任务组(Internet Engineering Task Force, IETF)提出,协议的第一个版本于1999年发布。最新版本是 [TLS 1.3](https://www.cloudflare.com/learning/ssl/why-use-tls-1.3/),发布于 2018 年。\r\n# 2. TLS与SSL\r\nNetscape开发了名为安全套接字层(Secure Socket Layer,[SSL](https://www.cloudflare.com/learning/ssl/what-is-ssl/))的上一代加密协议,TLS由此演变而来。TLS 1.0 版实际上最初作为SSL 3.1版开发,但在发布前更改了名称,以表明它不再与 Netscape 关联。由于这个历史原因,TLS 和 SSL 这两个术语有时会互换使用。\r\n>[!note]\r\n>SSL最终版本为SSL 3.0,从1996开始已经不更新了。SSL3.0存在很多已知安全风险,我们在应用不应该再使用了。同时,大多数浏览器也不再支持SSL3.0\r\n# 3. TLS发展\r\nTLS的最新版本是2018年发布的TLS1.3\r\n- **更安全**:TLS1.3放弃了对较旧、安全性较低的加密功能的支持\r\n- **更快**:加快了 [TLS 握手](https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/) ,TLS1.3中的TLS握手只需要一次往返(或来回通信)而不是两次,从而将过程缩短了几毫秒。如果客户端之前连接过网站,那么下次TLS握手的往返次数为零。这使HTTPS连接更快,减少[延迟](https://www.cloudflare.com/learning/performance/glossary/what-is-latency/)并改善整体用户体验\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250401221027.png)\r\n# 4. TLS基本概念\r\nTLS协议主要包括了三个部分:加密、认证和完整性保护\r\n- **Encryption:** 隐藏从第三方传输的数据。\r\n- **Authentication:** 确保参与信息交换的各方的身份都是真实的。\r\n- **Integrity:** 验证数据未被伪造或篡改。\r\n## 4.1. TLS证书\r\n网站或应用要使用TLS,必须在其服务器上安装TLS证书(旧称SSL证书)。TLS 证书由证书**权威机构**颁发给拥有域的个人或企业。该证书包含有关**域所有者**的重要信息以及服务器的**公钥**,两者对验证服务器身份都很重要。\r\n## 4.2. CipherSuites\r\n在TLS(传输层安全协议)中,加密套件(Cipher Suite)是一组用于在客户端和服务器之间建立安全通信的**加密算法和协议的组合**。它定义了在TLS握手过程中使用的具体加密机制,以确保数据的保密性、完整性和身份验证。\r\n#### 4.2.1.1. 组成部分\r\n- **密钥交换算法**:负责在客户端和服务器之间安全地交换会话密钥。常见的密钥交换算法有RSA、Diffie - Hellman(DH)、椭圆曲线 Diffie - Hellman(ECDH)等。例如,RSA算法可以用于服务器向客户端发送其公钥,客户端使用该公钥加密一个预主密钥并发送给服务器,双方再基于这个预主密钥生成会话密钥。\r\n- **身份验证算法**:用于验证通信双方的身份。通常依赖于数字证书和公钥基础设施(PKI)。常见的身份验证算法与密钥交换算法相关,如使用RSA算法进行服务器身份验证,客户端可以通过验证服务器证书中的公钥来确认服务器的身份。\r\n- **对称加密算法**:用于在建立会话密钥后对实际传输的数据进行加密和解密。常见的对称加密算法有AES(高级加密标准)、3DES 等。以AES为例,它具有较高的加密强度和效率,能够快速地对大量数据进行加密和解密操作。\r\n- **消息认证码(MAC)算法**:用于确保数据的完整性和真实性。在数据传输过程中,发送方会根据数据和会话密钥生成一个MAC值,并将其与数据一起发送。接收方在接收到数据后,使用相同的算法和会话密钥重新计算MAC值,并与接收到的MAC值进行比较。常见的MAC算法有 HMAC-SHA1、HMAC-SHA256 等。\r\n#### 4.2.1.2. 命名规则\r\n加密套件通常采用一种标准化的命名方式,即`TLS_密钥交换算法_身份认证算法_WITH_对称加密算法_消息摘要算法`。\r\n一个典型的加密套件名称可能是 `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`,其含义如下:\r\n- `TLS`:表示该加密套件基于TLS协议。\r\n- `ECDHE`:代表密钥交换算法为椭圆曲线 Diffie - Hellman(ECDH)的临时密钥交换模式(Ephemeral),提供前向保密性。\r\n- `RSA`:表示身份验证算法使用RSA。\r\n- `AES_128_GCM`:指对称加密算法为AES,密钥长度为128位,使用GCM(Galois/Counter Mode)模式,该模式结合了加密和认证功能。\r\n- `SHA256`:表示消息认证码(MAC)算法使用SHA-256哈希函数。\r\n比如`TLS_DHE_RSA_WITH_AES_256_CBC_SHA256`\r\n- 密钥交换算法是`DHE`\r\n- 身份认证算法是`RSA`\r\n- 对称加密算法是AES_256_CBC\r\n- 消息摘要算法是SHA256\r\n由于RSA又可以用于加密也可以用于身份认证,因此密钥交换算法使用RSA时,只写一个RSA,比如`TLS_RSA_WITH_AES_256_CBC_SHA256`\r\n#### 4.2.1.3. 套件的选择\r\n在TLS握手过程中,客户端会向服务器发送一个**支持的加密套件列表**,服务器会从这个列表中**选择一个双方都支持的加密套件**,并在握手过程中通知客户端。选择加密套件时,通常会考虑以下因素:\r\n- **安全性**:优先选择使用高强度加密算法和提供前向保密性的加密套件。\r\n- **性能**:不同的加密算法在性能上可能存在差异,需要根据实际应用场景进行权衡。\r\n- **兼容性**:确保客户端和服务器都支持所选的加密套件。\r\n# 5. TLS的通信过程\r\n完整TLS握手需要经过2-RTT\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202505142208829.png)\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250402222553.png)\r\n# 6. TLS协议解析\r\nTLS协议是一个分层协议,第一层为TLS记录层协议(Record Layer Protocol),该协议用于封装各种高级协议。目前封装了4种协议:握手协议(Handshake Protocol)、改变密码标准协议(Change Cipher Spec Protocol)、应用程序数据协议(Application Data Protocol)和警报协议(Alert Protocol)。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250402221259.png)\r\n\r\n抓包示例\r\n![[https_example.pcapng]]\r\n\r\n> [!note]\r\n> `Change Cipher Spec Protocol`在TLS1.3被去除。\r\n### 6.1.1. Record Layer Protocol\r\n记录层(Record Layer Protocol)包含协议类型、版本号、长度、以及封装的高层协议内容。记录层头部为固定5字节大小。\r\n![20200522091116.png](https://img2020.cnblogs.com/blog/580757/202005/580757-20200522091117417-275634995.png)\r\n\r\n> [!note]\r\n> 在TLS协议规定了,如接收到了未定义的协议协议类型,需要发送一个`unexpected_message`警报。\r\n### 6.1.2. HandShake Protocol\r\n握手用于确认认证双方身份并协商加密算法\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250402221528.png)\r\n[TLSv1.2](https://datatracker.ietf.org/doc/html/rfc5246)握手过程如下:\r\n```\r\n Client Server\r\n \r\n ClientHello -------->\r\n ServerHello\r\n Certificate*\r\n ServerKeyExchange*\r\n CertificateRequest*\r\n <-------- ServerHelloDone\r\n Certificate*\r\n ClientKeyExchange\r\n CertificateVerify*\r\n [ChangeCipherSpec]\r\n Finished -------->\r\n [ChangeCipherSpec]\r\n <-------- Finished\r\n Application Data <-------> Application Data\r\n```\r\n\r\n`*`表示可选步骤或与实际握手情况相关。比如重建已有连接,服务端无需执行Certificate,再比如使用RSA公钥加密时,无需ServerKeyExchange。 \r\n> [!note]\r\n> 完整的握手流程有时候也被称为2-RTT流程,即完整的握手流程需要客户端和服务端交互2次才能完成握手。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250402222553.png)\r\n- 当客户端连接到支持TLS协议的服务端要求创建安全连接并列出了受支持的算法套件(包括加密算法、散列算法等),握手开始。\r\n- 服务端从客户端的算法套件列表中指定自己支持的一个算法套件,并通知客户端\r\n- 服务端发回其数字证书,此证书通常包含服务端的名称、受信任的证书颁发机构(CA)和服务端的公钥。\r\n- 客户端确认其颁发的证书的有效性。\r\n- 为了生成会话密钥用于安全连接,客户端和服务端分别基于椭圆曲线算法生成一对公私钥,并将公钥发送给对方。\r\n- [[加密算法#3.2.1. ECDH协商简要过程|客户端和服务端分别基于自己的私钥和对端的公钥生成对称加密密钥]]\r\n\r\n握手协议的结构如下,其中协议头的ContentType固定为`22`,接下来是TLS版本号,TLS1.2为`0303`,最后是用2字节表示长度。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022308372.png)\r\nHandshakeType握手协议类型包含以下:\r\n- hello_request:0\r\n- client_hello:1\r\n- server_hello:2\r\n- certificate:3\r\n- server_key_exchange :12\r\n- certificate_request:13\r\n- server_hello_done:14\r\n- certificate_verify:15\r\n- client_key_exchange:16\r\n- finished:20\r\n> [!note]\r\n> Hello Message是具体的握手协议类型内容,不同协议内容有所不同。\r\n#### 6.1.2.1. client hello\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022316519.png)\r\n\r\n当客户端首次与服务端建立连接或需要重新协商加密握手会话时,需要将`Client Hello`作为第一条消息发送给服务端。\r\n`Client Hello`消息包含了许多重要信息,包括客户端版本号、客户端随机数、会话ID、密钥套件、压缩方式、扩展字段等。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022311071.png)\r\n- 客户端版本号:客户端支持的最新TLS版本号,服务端会根据该协议号进行协议协商。\r\n- 32位随机数:客户端生成的32位随机数。前4位是Unix时间戳,该时间戳为1970年1月1日0点以来的秒数。不过TLS并没有强制要求校验该时间戳,因此允许定义为其他值。后面28位为一个随机数。\r\n> [!note] \r\n通过前4字节填写时间方式,有效的避免了周期性的出现一样的随机数。使得\"随机\"更加\"随机\"。 在TLS握手时,客户端和服务端需要协商数据传输时的加密密钥。为了保证加密密钥的安全性。密钥需要通过客户端和服务端一起生成。客户端和服务端都提供一个32位的随机数,通过该随机数使用基于HMAC的PRF算法生成客户端和服务端的密钥。\r\n- 会话ID:用于表示客户端和服务端之间的会话。实际的会话ID是由服务端定义的,因此即使是新的连接,服务端返回的会话ID可能也会和客户端不一致,由于会话ID是明文传输的,因此不能存放机密信息。\r\n - 若会话ID是新的,则客户端和服务端需要建立完整的TLS握手连接流程。\r\n - 若会话ID是较早连接的会话ID,则服务端可以选择无需执行完整的握手协议。\r\n- 算法套件:客户端将支持的加密算法组合排列后发送给服务端,从而和服务端协商加密算法。服务端根据支持算法在ServerHello返回一个最合适的算法组合。 \r\n- 压缩方式:用于和服务端协商数据传输的压缩方式。由于TLS压缩存在安全漏洞,因此在TLS1.3中已经将TLS压缩功能去除,TLS1.2算法也建议不启用压缩功能。\r\n- 扩展字段:可以在不改变底层协议的情况下,添加附加功能。客户端使用扩展请求其他功能,服务端若不提供这些功能,客户端可能会中止握手。对于扩展字段的详细定义可以看[Transport Layer Security (TLS) Extensions](https://tools.ietf.org/html/rfc4366)\r\n> [!warning]\r\n客户端发送完 `ClientHello` 消息后,将等待 `ServerHello` 消息。 服务端返回的任何握手消息(`HelloRequest` 除外)将被视为致命错误。\r\n#### 6.1.2.2. server HEELO\r\n当服务端接收到`ClientHello`,则开始TLS握手流程, 服务端需要根据客户端提供的加密套件,协商一个合适的算法簇,其中包括对称加密算法、身份验证算法、非对称加密算法以及消息摘要算法。若服务端不能找到一个合适的算法簇匹配项,则会响应握手失败的预警消息。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022321226.png)\r\n- 版本号:服务端根据客户端发送的版本号返回一个服务端支持的最高版本号。若客户端不支持服务端选择的版本号,则客户端必须发送`protocol_version`警报消息并关闭连接。\r\n> [!note]\r\n> - 若服务端接收到的版本号小于当前支持的最高版本,且服务端希望与旧客户端协商,则返回不大于客户端版本的服务端最高版本。\r\n> - 若服务端仅支持大于client_version的版本,则必须发送`protocol_version`警报消息并关闭连接。 \r\n> - 若服务端收到的版本号大于服务端支持的最高版本的版本,则必须返回服务端所支持的最高版本。\r\n- 32位随机数:服务端生成的32位随机数,生成方式和客户端一样。服务端生成随机数的可以有效的防范中间人攻击,主要是通过防止重新握手后的[重放攻击](https://security.stackexchange.com/questions/218491/why-using-the-premaster-secret-directly-would-be-vulnerable-to-replay-attack)。\r\n- 会话ID:用于表示客户端和服务端之间的会话。若客户端提供了会话ID,则可以校验是否与历史会话匹配。\r\n - 若不匹配,则服务端可以选择直接使用客户端的会话ID或根据自定义规则生成一个新的会话ID,客户端需要保存服务端返回的会话ID当作本次会话的ID。\r\n - 若匹配,则可以直接执行1-RTT握手流程,返回ServerHello后直接返回`ChangeCipherSpec`和`Finished`消息。\r\n- 算法套件:服务端根据客户端提供的算法套件列表和自己当前支持算法进行匹配,选择一个最合适的算法组合,若没有匹配项,则使用默认`TLS_RSA_WITH_AES_128_CBC_SHA`。\r\n> [!note]\r\nTLS1.2协议要求客户端和服务端都必须实现密码套件`TLS_RSA_WITH_AES_128_CBC_SHA`\r\n- 压缩方式:用于和服务端协商数据传输的压缩方式。由于TLS压缩存在安全漏洞,因此在TLS1.3中已经将TLS压缩功能去除,TLS1.2算法也建议不启用压缩功能。\r\n- 扩展字段:服务端需要支持接收具有扩展和没有扩展的ClientHello。服务端响应的扩展类型必须是`ClientHello`出现过才行,否则客户端必须响应`unsupported_extension`严重警告并中断握手。\r\n> [!note]\r\n[RFC 7568](https://tools.ietf.org/html/rfc7568)要求客户端和服务端握手时不能发送`{3,0}`版本,任何收到带有协议Hello消息的一方版本设置为`{3,0}`必须响应`protocol_version`警报消息并关闭连接。\r\n\r\n通过`ClientHello`和`ServerHello`,客户端和服务端就协商好算法套件和用于生成密钥的随机数。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022317581.png)\r\n密钥交换算法:ECDHE \r\n身份认证算法:RSA\r\n对称加密算法:AES_128_GCM\r\n重要算法:SHA256\r\n#### 6.1.2.3. certificate\r\n假设客户端和服务端使用默认的`TLS_RSA_WITH_AES_128_CBC_SHA`算法,在`ServerHello`完成后,服务端必须将本地的RSA证书传给客户端,以便客户端和服务端之间可以进行非对称加密保证对称加密密钥的安全性。 \r\nRSA的证书有2个作用:\r\n- 客户端可以对服务端的证书进行合法性进行校验。\r\n- 对`Client Key Exchange`生成的pre-master key进行公钥加密,保证只有服务端可以解密,确保对称加密密钥的安全性。\r\n![20200526135243.png](https://img2020.cnblogs.com/blog/580757/202005/580757-20200526135246621-696721414.png)\r\n发送给客户端的是一系列证书,服务端的证书必须排列在第一位,排在后面的证书可以认证前面的证书。 当客户端收到了服务端的`ServerHello`时,若客户端也有证书需要服务端验证,则通过该握手请求将客户端的证书发送给服务端,若客户端没有证书,则无需发送证书请求到服务端。\r\n> 证书必须为[X.509v3格式](https://baike.baidu.com/item/X.509/2817050?fr=aladdin)。\r\n#### 6.1.2.4. Server Key Exchange\r\n使用RSA公钥加密,必须要保证服务端私钥的安全。若私钥泄漏,则使用公钥加密的对称密钥就不再安全。同时RSA是基于大数因式分解。密钥位数必须足够大才能避免密钥被暴力破解。\r\n> [!warning]\r\n> 1999年,RSA-155 (512 bits) 被成功分解。 \r\n> 2009年12月12日,RSA-768 (768 bits)也被成功分解。 \r\n> 在2013年的棱镜门事件中,某个CA机构迫于美国政府压力向其提交了CA的私钥,这就是十分危险的。\r\n\r\n相比之下,使用DH算法通过双方在不共享密钥的情况下双方就可以协商出共享密钥,避免了密钥的直接传输。DH算法是基于离散对数,计算相对较慢。而基于椭圆曲线密码(ECC)的DH算法计算速度更快,而且用更小的Key就能达到RSA加密的安全级别。ECC密钥长度为224~225位几乎和RSA2048位具有相同的强度。\r\n> ECDH:基于ECC的DH算法。\r\n\r\n另外在DH算法下引入动态随机数,可以避免密钥直接传输。同时即使密钥泄漏,也无法解密其他消息,因为双方生成的动态随机数无法得知。\r\n> 在密码学中该特性被称为[前向保密](https://zh.wikipedia.org/wiki/%E5%89%8D%E5%90%91%E4%BF%9D%E5%AF%86)\r\n> DHE: 通过引入动态随机数,具有前向保密的DH算法。 \r\n> ECDHE:通过引入动态随机数,具有前保密的ECDH算法。\r\n#### 6.1.2.5. Certificate Request\r\n当需要TLS双向认证的时候,若服务端需要验证客户端的证书,则向客户端发送`Certificate Request`请求获取客户端指定类型的证书。\r\n- 服务端会指定客户端的证书类型。\r\n- 客户端会确定是否有合适的证书。\r\n#### 6.1.2.6. Server Hello Done\r\n当服务端处理Hello请求结束时,发送`Server Hello Done`消息,然后等待接收客户端握手消息。客户端收到服务端该消息,有必要时需要对服务端的证书进行有效性校验。\r\n\r\n![20200526161422.png](https://img2020.cnblogs.com/blog/580757/202005/580757-20200526161424308-2087692628.png)\r\n#### 6.1.2.7. Client Certificate\r\n当客户端收到了服务端的`CertificateRequest`请求时,需要发送`Client Certificate`消息,若客户端无法提供证书,则仍要发送此消息,消息内容可以不包含证书。\r\n\r\n![20200526135243.png](https://img2020.cnblogs.com/blog/580757/202005/580757-20200526135246621-696721414.png)\r\n\r\n#### 6.1.2.8. Client Key Exchange\r\n客户端接收到ServerHelloDone消息后,计算密钥,通过发送`Client Key Exchange`消息给服务端。客户端和服务端通过`Key Exchange`消息交换密钥,使得双方的主密钥协商达成一致。\r\n\r\n![20200526175211.png](https://img2020.cnblogs.com/blog/580757/202005/580757-20200526175212460-1534904649.png)\r\n\r\n以RSA的密钥协商为例。在`ClientHello`和`ServerHello`分别在客户端和服务端创建了一个32位的随机数。客户端接收到`Server Hello Done`消息时,生成最后一个48位的预主密钥。通过服务端提供的证书进行公钥加密,以保证只有服务端的私钥才能解密。\r\n\r\n> 其中预主密钥的前2位要求使用`Client Hello`传输的TLS版本号(存在一些TLS客户端传递的时协商后的TLS版本号,对该版本号检查时可能会造成握手失败)。\r\n\r\n需要注意的是,若RSA证书的填空格式不正确,则可能会存在一个漏洞导致客户端发送的PreMasterSecret被中间人解密造成数据加密的对账密钥泄漏。可以看下[Attacking RSA-based Sessions in SSL/TLS](https://eprint.iacr.org/2003/052)\r\n#### 6.1.2.9. Certificate Verify\r\n若服务端要求客户端发送证书,且客户端发送了非0长度的证书,此时客户端想要证明自己拥有该证书,则需要使用客户端私钥签名一段数据发送给服务端继续验证。该数据为客户端收发的所有握手数据的hash值(不包括本次消息)。\r\n\r\n![20200619203948.png](https://img2020.cnblogs.com/blog/580757/202006/580757-20200619203949761-1258998351.png)\r\n#### 6.1.2.10. Finished\r\n当发送完`Change Cipher Spec`消息后必须立即发送该消息。当该消息用于验证密钥交换和身份验证过程是否成功。\r\n\r\n![20200526175339.png](https://img2020.cnblogs.com/blog/580757/202005/580757-20200526175340642-1806058204.png)\r\n\r\n`Finished`消息是第一个使用协商的算法簇进行加密和防篡改保护的消息。一旦双方都通过了该消息验证,就完成了TLS握手。 \r\nVerifyData为客户端收发的所有握手数据的hash值(不包括本次消息)。与`Certificate Verify`的hash值可能会不一样。如果发送过`Certificate Verify`消息,服务端的握手消息会包含`Certificate Verify`握手的数据。\r\n\r\n> 需要注意的是,握手数据不包括协议头的握手协议明文数据(服务端返回`Finished`的验证握手数据是包含接收到客户端的`Finished`的明文hash值)。\r\n\r\n> `Finished`消息数据加密和`Appilication Data`一致,具体数据加密在`Application Data`段进行说明。\r\n### 6.1.3. Change Cipher Protocol\r\n在此之前,双方通信基本是明文或处于密钥协商未完成状态,该消息发送后,后续消息切换为用选定的对称加密算法对后续数据加密后再传输,确保数据的机密性和完整性 。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022255474.png)\r\n### 6.1.4. Alert Protocol\r\n警报消息传达消息的严重性(警告或致命)和警报的说明。具有致命级别的警报消息会导致立即终止连接。 若在改变密码标准协议前接收到警报消息,是明文传输的,无需解密。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022336824.png)\r\n与其他消息一样,警报消息按当前连接状态指定进行加密和压缩。在接收到改变密码标准协议后接收到警报协议,则需要进行解密。解密后即为警报协议明文格式。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022336612.png)\r\n### 6.1.5. Application Data Protocol\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022256036.png)\r\n当客户端和服务端`Finished`发送完毕并验证通过后,握手就结束了。后续所有数据都会使用握手协商的对称密钥进行数据加密。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202504022336114.png)\r\nTLS协议实现了数据加密和MAC计算。一般来说有3种加密模式,分别为:\r\n1. Mac-then-Encrypt:在明文上计算MAC,将其附加到数据,然后加密明和+MAC的完整数据。\r\n2. 加密和MAC:在明文上计算MAC,加密明文,然后将MAC附加到密文的末尾\r\n3. Encrypt-then-Mac:加密明文,然后在密文上计算MAC,并将其附加到密文。\r\nTLS协议使用的是`Mac-then-Encrypt`。首先将加密的序号、ContentType、数据长度、数据进计算HMAC-SHA256摘要。然后将摘要拼接到数据后,通过PKCS7格式对摘要+MAC数据进行填充对其和加密块大小一致。最后对`明文+MAC+对其填充块`进行加密。\r\n\r\n需要注意的是应用程序数据消息有最大长度限制`2^14 + 2048`,当超过长度后,数据需要分段传输。每一段都当作单独的数据段进行单独MAC地址并加密。\r\n# 7. TLS1.3\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/tls1_3.png)\r\nTLS 1.3 的握手不再支持静态的 RSA 密钥交换,这意味着必须使用带有前向安全的Diffie-Hellman进行全面握手。客户端发送Client Hello时,附带了DH算法的公共参数和公钥,服务器返回Server Hello时,附带了DH算法生成的服务器公钥,即一次传输就完成了密钥交换。\r\n\r\n|**特性**|**TLS 1.2**|**TLS 1.3**|\r\n|---|---|---|\r\n|**密码套件数量**|数十种(包含不安全选项)|仅 5 种官方套件(均为安全选项)|\r\n|**密钥交换**|支持 RSA、DHE、ECDHE 等多种方式|仅支持 ECDHE(强制前向安全性)|\r\n|**对称加密**|支持 CBC、GCM 等模式|仅支持 AEAD 模式(如 GCM、Poly1305)|\r\n|**协商 RTT**|至少 1 个 RTT(复杂场景需 2 个 RTT)|仅需 1 个 RTT|\r\n|**降级攻击防护**|需要 `fallback SCSV`|无需(移除不安全套件)|\r\n## 7.1. 0-RTT\r\nTLS 1.3 的 0-RTT(零往返时间)特性允许客户端在首次与服务器建立连接后,后续连接时直接发送应用数据,无需等待TLS握手完成,从而消除了传统握手所需的至少 1 个 RTT 延迟。这一特性通过**会话恢复**和预共享密钥(PSK)机制实现。\r\n### 7.1.1. 基本原理\r\n0-RTT 的核心在于客户端和服务器之间**预先共享密钥材料**,使得客户端可以在新连接的第一个消息中就发送加密的应用数据。这依赖于两个关键技术:\r\n- **PSK(预共享密钥)**:服务器在首次握手时生成并存储一个会话密钥,客户端也保存该密钥的副本。\r\n- **Early Data(早期数据)**:客户端使用PSK加密应用数据,并在新连接的初始消息中直接发送。\r\n### 7.1.2. 建立过程\r\n**首次握手(建立 PSK)**\r\n当客户端与服务器首次建立 TLS 1.3 连接时(使用 1-RTT 握手),服务器会生成一个**会话票证(Session Ticket)**,其中包含:\r\n- **PSK(预共享密钥)**:用于后续会话的加密密钥。\r\n- **Ticket 有效期**:通常较短(如几小时),以平衡安全性和可用性。\r\n- **Ticket 标识**:用于服务器识别该会话票证。\r\n服务器将此会话票证加密后发送给客户端,客户端将其存储在本地(如浏览器缓存)。\r\n**后续握手(使用 0-RTT)**\r\n当客户端再次连接同一服务器时,流程如下:\r\n1. **客户端发送 ClientHello**:\r\n - 包含之前存储的会话票证(PSK 标识)。\r\n - 标记 `early_data` 扩展,表示将发送 0-RTT 数据。\r\n - 直接携带使用PSK加密的**应用数据**(如 HTTP 请求)。\r\n2. **服务器验证PSK**:\r\n - 检查会话票证的有效性和未过期。\r\n - 使用对应的 PSK 解密 0-RTT 数据。\r\n3. **服务器响应**:\r\n - 发送ServerHello消息,完成标准的TLS 1.3握手(1-RTT)。\r\n - 在此期间,服务器可以处理并响应0-RTT数据。\r\n```\r\nClientHello {\r\n ...\r\n extensions: [\r\n ...\r\n pre_shared_key: {\r\n identities: [\r\n {\r\n obfuscated_ticket_age: ,\r\n ticket: <会话票证数据>, # 包含 PSK 标识\r\n ...\r\n }\r\n ],\r\n binders: , # 证明客户端拥有对应 PSK\r\n },\r\n early_data: <是否发送 0-RTT 数据>, # 与 PSK 关联\r\n ...\r\n ]\r\n}\r\n\r\nServerHello {\r\n ...\r\n extensions: [\r\n ...\r\n pre_shared_key: {\r\n selected_identity: <客户端提供的票证索引>, # 服务器选择的 PSK\r\n },\r\n early_data: <是否接受 0-RTT 数据>, # 与 PSK 关联\r\n ...\r\n ]\r\n}\r\n```\r\n\r\n### 7.1.3. 优缺点\r\n#### 7.1.3.1. 优点\r\n- **极致性能**:完全消除握手延迟,适用于对延迟敏感的应用(如实时通信、金融交易)。\r\n- **简化流程**:无需等待密钥交换和认证,直接发送数据。\r\n#### 7.1.3.2. 缺点\r\n- **重放攻击风险**:由于0-RTT数据使用静态PSK加密,攻击者可能截获并重复发送相同的数据。例如,重复提交支付请求。\r\n- **有限的前向安全性**:PSK基于之前的会话,若该会话密钥泄露,0-RTT数据可能被破解。\r\n- **服务器限制**:服务器可能选择不接受 0-RTT 数据(如处理幂等性操作时)。\r\n### 7.1.4. 安全防护机制\r\n为降低风险,TLS1.3对 0-RTT 施加了以下限制:\r\n- **幂等性要求**:建议0-RTT数据仅用于幂等操作(如读取请求),避免执行重复提交会产生副作用的操作(如支付)。\r\n- **PSK 有效期**:会话票证的有效期较短,减少被滥用的时间窗口。\r\n- **Early Data 指示**:服务器可以通过 `EarlyDataIndication` 扩展明确告知客户端哪些数据是通过 0-RTT 接收的,便于应用层处理。\r\n### 7.1.5. PSk\r\nPSK 通过先前的TLS会话(如首次 1-RTT 握手)生成并分发,客户端和服务器各自存储一份副本,用于后续会话恢复。\r\n```\r\n客户端 → 服务器:ClientHello\r\n服务器 → 客户端:ServerHello, EncryptedExtensions, Certificate*, CertificateVerify*, Finished\r\n客户端 → 服务器:Finished\r\n服务器 → 客户端:NewSessionTicket // 会话票证在此处发送\r\n```\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202505142224574.png)\r\n会话票证(`ticket` 字段)是一个加密的二进制数据,通常包含:\r\n- **PSK(预共享密钥)**:用于后续会话恢复。\r\n- **票证 ID**:服务器用于识别该票证的唯一标识。\r\n- **绑定数据**:确保票证只能用于特定的服务器配置。\r\n- **加密参数**:如密钥派生函数和加密算法。\r\n# 8. MTLS\r\nmtls(mutual tls)通过双向认证,可以保证连接的两端都有对应的私钥,以此来验证两端的身份合法性。mtls通常被用于zero trust安全框架,以验证组织内的用户、设备和服务器。[[mtls实现]]中提供了java版本的客户端和服务端实现。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250606220928.png)\r\n在mTLS中,客户端和服务器都有一个证书,并且双方都使用它们的公钥/私钥对进行身份验证。与常规TLS相比,mTLS中需要**服务端验证客户端证书**。\r\n> [!note]\r\n> 使用身份证书相对于appcode来说,更为安全,避免了appcode在网络中传输、泄露的风险。\r\n- **中间人攻击**:中间人攻击者把自己放在客户端和服务器之间,拦截或修改两者之间的通信。当使用mTLS时,在途攻击者不能对客户端或服务器进行身份验证,使这种攻击几乎不可能进行。\r\n- **欺骗攻击**:攻击者可以试图在用户面前“伪装”(模仿)Web 服务器,或在 Web 服务器面前伪装用户。当双方都必须用 TLS 证书进行身份验证时,欺骗攻击就会困难得多。\r\n- 暴力攻击:暴力攻击执行,是指攻击者使用快速试错法来猜测用户的密码。mTLS确保一个密码不足以获得对组织网络的访问权。\r\n- **网络钓鱼攻击**:\r\n- 目的通常是为了窃取用户的凭据,然后利用这些凭据入侵网络或应用。即使用户上当受骗,攻击者仍然需要TLS证书和相应的私钥才能使用这些凭据。\r\n- 恶意 API 请求:当用于API安全时,mTLS可确保API请求只来自合法的、经过身份验证的用户。这可以阻止攻击者发送恶意的API请求来利用漏洞或破坏API的预期运作方式。\r\n> [!note] 与token、session/cookie等关系\r\nmtls可以更安全地实现身份认证,但无法提供token、session/cookie等提供的权限控制能力。比如token中可以通过携带服务端签名的角色、权限等信息。\r\n# 9. SNI(Server Name Indication)\r\nWeb服务器或负载均衡器承载多个域名,仅IP地址不足以指示用户尝试访问哪个域。这可能会导致服务器显示错误的SSL 证书,从而阻止或终止 HTTPS 连接。\r\n> [!note]\r\n> 当多个网站托管在一台服务器上并共享一个IP地址,并且每个网站都有自己的SSL证书,在客户端设备尝试安全地连接到其中一个网站时,服务器可能不知道显示哪个 SSL 证书。这是因为SSL/TLS 握手发生在客户端设备通过HTTP 连接到某个网站**之前**(http host header)。\r\n\r\nSNI是TLS协议的扩展,以确保客户端设备能够看到他们尝试访问的网站的正确SSL证书。该扩展使得可以在TLS握手期间指定网站的主机名或域名 ,而不是在握手之后打开HTTP连接时指定。\r\n\r\n简而言之,即使网站`https://www.example.com`托管在与`https://www.something.com`、`https://www.another-website.com`和`https://www.example.io`相同的地方(相同的IP地址),SNI也能让用户的设备与`https://www.example.com`建立安全的连接。\r\n\r\nSNI在2003年被添加为TLS/SSL的扩展;它最初不是协议的一部分。几乎所有的浏览器、操作系统和Web服务器都支持它,除了一些仍在使用的最旧的浏览器和操作系统。\r\n# 10. ref\r\nhttps://segmentfault.com/a/1190000021494676\r\nhttps://segmentfault.com/a/1190000021559557\r\n[TLS1.2协议设计原理](https://www.cnblogs.com/Jack-Blog/p/13170728.html)\r\nhttps://developer.aliyun.com/article/1245100"},{"id":"常用http header","title":"常用Http Header","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"常用http-header","description":"1. remoteaddr 表示发出请求的远程主机的IP地址,remoteaddr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时 1. 假设中间没有任何代理,那么网站的web(,Apache等)就会把remoteaddr设为你的机器IP 2...","relativePath":"Tech/Network/应用层/常用http header.md","rawContent":"# 1. remote_addr\r\n表示发出请求的远程主机的IP地址,remote_addr代表**客户端的IP**,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时\r\n1. 假设中间没有任何代理,那么网站的web[服务器](https://www.baidu.com/s?wd=%E6%9C%8D%E5%8A%A1%E5%99%A8&tn=24004469_oem_dg&rsv_dl=gh_pl_sl_csd)([Nginx](https://www.baidu.com/s?wd=Nginx&tn=24004469_oem_dg&rsv_dl=gh_pl_sl_csd),Apache等)就会把remote_addr设为你的**机器IP**\r\n2. 如果你用了某个代理,那么你的浏览器会先访问这个代理,然后再由这个代理转发到网站,这样web服务器就会把remote_addr设为这台**代理机器的IP**\r\n# 2. http_x_forwarded_for\r\n在 HTTP 代理和负载均衡等场景中,用于标识通过HTTP代理服务器连接到Web服务器的客户端的原始IP地址。\r\n\r\n由于你使用了代理时,web服务器就不知道你的真实IP了,为了避免这个情况,代理服务器通常会增加一个叫做http_x_forwarded_for的头信息,把连接它的客户端IP(即你的上网机器IP)加到这个头信息里,这样就能保证网站的web服务器能获取到真实IP\r\n格式一般为:\r\n```\r\nX-Forwarded-For: 1.1.1.1, 2.2.2.2, 3.3.3.3\r\n```\r\n代表 请求由`1.1.1.1`发出,经过两层代理,第一层是`2.2.2.2`,第二层是`3.3.3.3`\r\n# 3. X-Real-IP\r\n`X-Real-IP`是一个自定义的 HTTP 请求头字段,常用于标识客户端的**真实IP地址**。在存在代理服务器(如反向代理、负载均衡器等)的网络环境中,服务器直接获取到的 IP 地址往往是代理服务器的 IP 地址。为了能获取到客户端的真实 IP,就可以使用 `X-Real-IP`请求头。\r\n\r\n当请求经过代理服务器时,代理服务器会将客户端的真实IP填充到 `X-Real-IP` 这个请求头字段中,然后再将请求转发给后端的 Web 服务器。Web 服务器通过解析这个请求头,就可以获取到客户端的真实 IP 地址。\r\n> [!note]\r\n> - X-Forwarded-For一般是每一个非[透明代理](https://www.baidu.com/s?wd=%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86&tn=SE_PcZhidaonwhc_ngpagmjz&rsv_dl=gh_pc_zhidao)转发请求时会将上游服务器的IP地址追加到X-Forwarded-For的后面,使用英文逗号分割 \r\n> - X-Real-IP一般是第一级代理将客户端IP地址添加到该头中\r\n> - remote_addr代表最后一个代理服务器的ip\r\n"},{"id":"应用层","title":"应用层","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"应用层","description":"","relativePath":"Tech/Network/应用层/应用层.md","rawContent":""},{"id":"ARP","title":"ARP","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"arp","description":"1. arp响应的条件 1.1. 普通主机响应 ARP 请求的条件 当本机是普通主机时,只有当 ARP 请求中的目标IP地址与本机IP地址匹配时,普通主机才会发送ARP响应。例如,主机A的IP地址为192.168.1.10,当它接收到ARP请求,且请求的目标 IP地址为192.168.1.10时,主...","relativePath":"Tech/Network/数据链路层/ARP.md","rawContent":"# 1. arp响应的条件\r\n## 1.1. 普通主机响应 ARP 请求的条件\r\n当本机是普通主机时,只有当 ARP 请求中的目标IP地址与本机IP地址匹配时,普通主机才会发送ARP响应。例如,主机A的IP地址为192.168.1.10,当它接收到ARP请求,且请求的目标 IP地址为192.168.1.10时,主机A会回复 ARP 响应,在响应中携带自己的 MAC 地址。\r\n## 1.2. 网关响应 ARP 请求的条件\r\n- 标准情况:网关收到 ARP 请求,如果请求的目标 IP 地址是网关自身某个接口的 IP 地址,网关就会发送 ARP 响应。例如,网关连接到 192.168.1.0/24 网段的接口 IP 为 192.168.1.1,当收到 ARP 请求且目标 IP 为 192.168.1.1 时,网关会回复 ARP 响应,告知自身的 MAC 地址。\r\n- 代理 ARP 情况:在启用代理 ARP 的情况下,网关收到 ARP 请求,若请求的目标 IP 地址不属于网关自身,但网关知道如何到达该目标 IP(即网关有到目标 IP 所在网络的路由),网关会代替目标主机进行 ARP 响应。比如,在一个包含多个子网的网络环境中,子网 A 的主机向网关发送 ARP 请求,询问子网 B 中某主机的 IP 地址对应的 MAC 地址,网关开启了代理 ARP 功能,且有到子网 B 的路由,网关就会回复自己的 MAC 地址给子网 A 的主机,使得子网 A 的主机将发往子网 B 主机的数据包先发送给网关,由网关转发。\r\n\r\n如果 ARP 请求的目标 IP 地址属于网关管理网关,但是目标IP却不是网关IP时,说明该目标主机与发送请求的主机处于同一子网,理论上不需要网关进行转发,网关会丢弃这个 ARP请求。这是因为在同一子网内,主机之间可以直接通过二层网络通信,通过 ARP 获取目标主机的 MAC 地址后直接进行数据传输。例如,子网A的网络地址为 192.168.1.0/24,主机 192.168.1.10 向网关192.168.1.1发送ARP请求,询问 192.168.1.20 的 MAC 地址,网关发现192.168.1.20属于同一子网,便会丢弃该请求。(192.168.20这个地址会有二层可达的主机回答,而不需要网关代答)"},{"id":"VLAN","title":"VLAN","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":47,"slug":"vlan","description":"1. 介绍 当网络上所有设备在同一个广播域产生大量的广播和多播帧(arp/dhcp/stp/rip),就会与业务数据流争带宽,造成网络性能恶化。将大型广播域分段是提高网络性能的方法之一。路由器能够将广播包阻隔在一个三层接口上,但是路由器的LAN接口数量有限,它的主要功能是在三层网络间传输数据,而不是...","relativePath":"Tech/Network/数据链路层/VLAN.md","rawContent":"# 1. 介绍\r\n当网络上所有设备在同一个广播域产生大量的广播和多播帧(arp/dhcp/stp/rip),就会与业务数据流争带宽,造成网络性能恶化。将大型广播域分段是提高网络性能的方法之一。路由器能够将广播包阻隔在一个三层接口上,但是路由器的LAN接口数量有限,它的主要功能是在三层网络间传输数据,而不是对终端设备提供网络接入。访问LAN的功能还是由接入层交换机来实现,通过在二层交换机上创建VLAN来减少广播域。现代交换机就是通过VLAN来构造的,因此在某种程度上,学习交换机就是学习VLAN。大多数交换机都存在一个默认的VLAN1信息,这是交换机出厂时的默认设置,把所有的端口都划分在了VLAN1中。\r\n# 2. VLAN和VLSM\r\n![base64str](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf8AAAD3CAIAAACct8owAAAAAXNSR0IArs4c6QAATYdJREFUeF7tnQl4U8e1+CVrlyVZtuUN77uNDV7AgRjCErIBCaFNS7Y2kIbAa0gftGnea0vakH7Ja9Mt8G/SBpKXlm5kaVJ4Zc0COMHsi40NeJGNd8uyZcuWZEuyLP3P1TVClmRJtuUr+ercKP6ENNv5ndG5c8+cmWFaLBYGXkgACSABJBBkBEKCTF4UFwkgASSABAgCaP2xHyABJIAEgpEAWv9g1DrKjASQABJA6499AAkgASQQjATQ+gej1lFmJIAEkABaf+wDSAAJIIFgJIDWPxi1jjIjASSABND6Yx9AAkgACQQjAbT+wah1lBkJIAEkgNYf+wASQAJIIBgJoPUPRq2jzEgACSABtP7YB5AAEkACwUgArX8wah1lRgJIAAmg9cc+gASQABIIRgJM3OF5Qmo3mkaq5R21zcralp61K4oL06ImlD1AEuuGjLXNXSDFzY7eVUsLZ6gUAQITm4EEZigBtP6eFdenGaySd9a1KKsbOhrbVWSGgtxUnkT60roSz/kDIwW0HCx+XUs3/G1TqslG3TE3kyEQzSApAoMltgIJ0IEAWn/XWgT7WCXvAFtZ1dCh7NVAIpVW06MdUOt0C9Kz4J8rFheHhYVuuDu3SaE6eqH6QHnFhgdXhgn5sVJBgkwkkwj83jtsA/zqxs7api54anGW4oFl84WhApCiQt4KUpRV1j2zZrWIzw0cKfyOERuABOhKAK3/bc2Coa9tUsIYH96A6RweMfUN6sDow6tPpyXTRYrFpek58ObuJSVqTe+Br8rBbpJf7dz2XJ/WSL5ns0LgHpATH74wO5bKruM8wHcvxeLS4s5uxftfnFT09pPtfPOF57v79f6VgkpiWBcSCFoCaP1HVQ8D/I2v7TMMD/cNaq3DfM3A0KBzt8iKnZUdGy/gcXPn5O49dKips9OW5vUtGw99ccYwYmJx2Ex2iCw8Yllh/rY1hVT2LRABBPFSCnGoIDM3y0GKXz+/6d+fl/tXCiqJYV1IIGgJYMzPqOov1bSC0f/0WsWFm/LG7i6Xph+SykQS+JuVFg+z5W1KpX2/MRiMOVmZMTKZkM3lmUIGVaOjacr6FnirwPR7L0VGcpxpZMRBiiG93r9SUIYLK0ICQU4Arf9oB7hS2wZufY+9IVwYCmkiwqWtyi4wnfbptXp9U2ubXm/gcjlsHkcgJlJSeV2uIXxQ3kuhGzG3dSsDTQoqiWFdSCCYCaD1J7QPM6JgOhX9avddQSIQhoQQxAYMwzfbOxwSd6v775xXCK+SwrnwtygvVyriUdm3zl1rhuq8l8JoYTZ33PZckU31uxRUEsO6kEAwE0DrT2gfgiDVg0PjeXts/UMmEsN7FotlYbJqmglTa3/1Dej82JNgmhqClAaNRi+l4PO5ERKJsxRDeoMfpcCqkQASoIwAWn8C9bnqZpUXbp9Iq/VPio/WG40K1Wjgv01VxuFhytTmXNHZ6pvwofdSgG/KpRQDuiE/SoFVIwEkQBkBtP4EanD7qLSjMZ1u0JPWn8nl2If62NJr+nWdHV3wUnQqyTfdKjVliqxuUFitv7dSjISwAlAKynBhRUgACaD1Z0CcDBEto/FgqUV8PofFJnoMi91sF+hp60MsPjduVgy8YuOiyTc6E0V4Yd6CHPt7L4UsIjzQpMBfIxJAAlQSoMg8USnSROuCWE9YzAUx8u4zhgtHnf5SsdjlqHmi9fowPWw9BH5/76UYNpujwqWBJoUPgWBRSAAJeCSA1p8BsZ5KjefY/EiRCGiKJaEu3eXwVZhYDJMBew8f+vvRoycvX4aXiM/xqACfJLhc22Yd+HsrBZPLdiOFvK0NpDhUXg4iQLGUSeETFFgIEkACXhIIdutPxnqqdMROPu6vSOs6rxGWa3c5mVc7NLS0uHhRYUHKrDh4wX4Pnkr1zfdnq5ugIO+lCBUJ3Qz8IewHpMhLTwMRGExi1wrftBJLQQJIIJAIBPsPG2I9dQaDSuPB+vM4HCGXC4qLkIa5dJcTY2SBgMvmxEZEnKu61tDalhIbJxJQMfaHeQt4wX4+3ksRFRnhRgoOm22TgmEBuaiQIpB+FNgWJBAUBILd+kOsp8flUdARwoWE2wci/SOlYeONmvlcrsFoqKirr2luOlVZOTikp2bU/OWVBmib91IYR8zR4eFupIDSbFLAe2qkCIpfGwqJBAKJQLBbf/CZdA943uCBjPXkCvjjucutY38hh8PRDY3Gy4+YR6jxmJNLfL2XIoQ3rtOflILJYNikID6havYikH4X2BYkQH8CQW39SZ8JuXmD+4tc5QvW3427XMDnwZ452kHC+rNZLP2wkYJRM7mJP9TovRTunf4gBRRlkwLH/p66Bn6PBGYqAc+Gb6ZK5kW7ycnSwqSUFbML5iamJEZEwfb9o0H9dtnhE9jhBz6QSsXjucvhW5gb4HKIBbTEeFkoNFssFIyayTD/CUnhxulPSsHmEA8HpBQw60uBFF7oCpMgASTgYwJBbf3JQEm4YEY3OTIKbgNwcssDc4oeKiyxf8EnkIZw+oeFydtGz3Jx0INUTEwMmM1m/TCxT451DmCYHQJOlOm9IFzVVoE3UpBOf/dSMBgWmxQw60uBFNPLCEtHAkjAFYGgtv7ZSdFcNovEkpGZZvvrsquA20et0faoXcfUg6uHMPo8rt5AjJrhTQiTScGoOSUuMlRABCPZS+Gmq4PT36MUAh7PJgUURYEU+NtEAkiAegJBbf0fv3/eP17bsPWxpcU5ifL6RqBP/oUrOTUpMyfdXh+E01/huB+yLQFMlsJ7JpM56jPhC2EOgAK//zdWFO7d8e0ffmvFkqJ0uA3Y2j/ezYBw+nuSAvLapID3FEhBfb/HGpEAEmDt2LEjmCmwQkLS4mXL5mU+sqKwICsengYOnb9qMA13KrvVKjU5oiev2PiYqvp6RW+vS1wpcXE5KSng+enpUyv7+u5dsADmTu8pSo8Q86cbL4iQHBdROjftG3cXFuckFGQmJMdGlF+TwzoGpbIH/Pj2DYiLj62Wyz1JYbFJAbtAz8+MpkCK6aaE5SMBJOBAAM/1dewSuU//tKaF2C+TvF7euBH+vvLuu+67zuKCghUlJQM6nSR09EivTpXqx+sWp0QTK4Spv1Kf/HGTosdehMlJAbk2rMj1lxTUc8MakUDwEAhqz49LNcdGhE1C/eRTAhkoCRe4faKk4X70mKfERk5CCj6XeFDo144eU2OxFuFHKSYhAmZBAkjASwJo/R1BxYZPxvrHR8ugICGfT+4V2tnTA+5yP3rM+WMdPtY706DHPkFKARfp9ycjlvwohccGYwIkgAQmTQCtP4HuvhffeO6Nf2zb+fH2Px7s6nZxQMoapjFb725JcGy4FMqBuE/Sz54YE0P9qNleCp3OccNqLou9LD/XeTWDfdeJlxFPDIkx0RCxavscx/6T/nVhRiQQyATQ708OdYePXqg+cq66Qt7GZrIYJpbeZOwe0Cj6+7auW9d8s0WvN7BNw3fOz2FzLUrNACSukI8J/P/5pqdHzCyIB21ub+vTaCwmUyiXC4uIxSL+lq8vWVaQTUEncJDCqLeoh3StvT0vPPFEl6Jb1dM7MjICEa6ZyVGtf32vWTdYnZqr1o55IHh9y7ODBot/paAAFFaBBJAA8XBvsZDe3SC6mpQDbT3aNpXONGKenxGdkxBuLzyY9X+dunLobJXBMMIN4USLw+zHyyIBvzg/7YePLQZTe7Ky9uz1xmMXr8Hf15/bfOzc2eOXKl1yfPmph3asX+NbxFORgm8yZg31vrrnZ00KFUhx7kYj/IW57t9+77lDp8uplMK3TLA0JIAEvCcQLNZf3qmWdw60qTRg9x3oJMhE9xQkOoe1KHr7Pzx56cMTF7t6NbmxifHD2mUP33P/wmxpKM8lX7Ce+8uvNHepalqJkCG4JcAdgkwpFQn7DuzyXivjpZy6FBnJsQvyUqSPP8Dv7lzq6sZPgRRT54AlIAEkMHUCwWL9X9j9CZsXIeAJxkOWEiN5oCg5NpxYtOV8lTGJGVCX5nLqOvC+hClKYasoQMTxXnBMiQSQgM8JjD/rK9+1iMncfISo0fr29rVolxw+PLL51idkIofLLjvxjS21N4lvFWWr12WmCbF47emHQkx9F2tqxsvV1DXw9tGq97+qV+uIjXoC86KHFIHJFluFBIKNwDjWH+xu5rbTt2BkbC2H6QG46neWMhil61ZngDVftYex6bD1oz2rHK3z2OzE3WPVHiLx4U0Mj4ltGpDvWr+NsbOeyLVnFXnHmfwFkey/2Pi1lx6963x1BQTjj1dQTVvvzv+r2PG3LyZf03TmpIcU00kIy0YCSMBbAq6sv4PtHmOOT5fu3Ls1g8FYuRtuBrtXjn5XXWdnm52yyw99eJqxaS0kHpuLyDxeXfANkSs/C+rKnF3KOH293luR3KRbODvtk5e/069udd6sDSLcqxsbD5SV7frg/QNflfugsmkrgh5STBseLBgJIAGvCIwz9oeBOozTx15Hfg1PA5teImz/7ctqo61PA/aXc/bS6ldJR5GzE8dVXVBY/XXbswdR9JgbjFeiuU4Ew+ffP//oyqJZ5C7Hit6em21NxiGllKt9/sHif+14pm//G1d2/2wKNVCRlR5SUEEK60ACSGA8AqRLx8Vltf6Eu2b0Iv5dSjhi7C5rGscPye/tst9ORXqOHAoZm3hMfWT94+Uat+m+/+IkgwEv35frpxJpJo6fKGK1SGBmE/B6re+R/XsYpCPm1mX15oN9Lh/7NOB8myE8N2TWjKx8hrdOHGuu29eYqvFmjgSQABJAAlMj4K31l9dVw5MA4bsfvY5shmlhGMbbfP8OQT52zcpYva6U9NzYShk/MRlgBP4ha649+yGeiPABlc7OnJqgmBsJIAEkgATsCHhr/R288PJdr8KjAOP0tkyrM99DRA7EDB3OJ5LCDWPTYdv9woMiMrbuJeKJmEwiXsjjAwZqFQkgASSABCZAIFhWe00AiaukNFseRTNxpqhczB5sBIymkWp5R22zsralZ+2K4sK0qGAjQMqL1t8rvdPMXNJMHK9UiImCm0CfZrBK3lnXoqxu6GhsV5EwCnJTeRLpS+tKgpMNWn+v9E4zc0kzcbxSISYKPgJtSnWVvKOupbuqoUPZqwEAfTqtSqvpG9SVpBIBLCsWF4eFhW64Ozf42ODY32ud08xc0kwcr9WICelPAAx9bZMSxvjwRjdEHFJEWnx49eg0cPI2fBIpFpem58Cbu5eUgNtn2ZwE2NkXtm0vq6x7Zs1qPpcdGyZIkInH2/WLNhBx7O+VKmlmLmkmjlcqxERBQAAG+Btf2+fS4ttLnxETlxuXIOBxc+fkdnU37//qPGzoSyZ484XnPzx0wjBiYnHYTHZITvKsb61YCLcHWsLzNuaHlsKjUEgACdCJwKWaVq1ef+jqpVP1N250tik1/eRg3+EKF4bCJ1lp8bBY692Dx22mHz4cMhhzsjJjZDIhm8szhbTc7Kq42UMnRPayoPWnq2ZRLiTgBwLwWEk+WfrlulI7rsW3b0+kSAz/FEvEzV0Khz0f4fjryhs3hgaHwiSipPi47Mw0vwhCTaVo/anhjLUgASQwvQQgjvNyTWv3wKgPZ7zKhFweeVpfv2G4uaPTIZlWb5hfMFcoFber+z6vvPy3zz9js2hrJGkr2PR2NCwdCSCBACNQ29wFp+nB1K77dkWKJJCAxQLHPremudkhce/AgEwaVpiZef/Chc+sWbP10UdFAk6ACeqz5qD19xlKLAgJIAE/EjhX3WyL6nHTDImAOOAvOkoKm7orVKOB/7b0Gt2QH0WguGq0/hQDx+qQABKYFgLg9oHgTo9Fy6xOfyMzpKnT0e0Dn2v6dZ0dXfBSdCrJN719Ax7LnKEJ0PrPUMVhs5EAErhNAGI9YW1Xt8aDpQaPv0RAnN0tCg1tdmX9+42GuFkx8IqNiybfmEPQ84M9DQkgASQQqAQg1nN4xORx7C8REm4fcPpHSsNcjv35XK6DiFr9cKAKPdV24dh/qgQxPxJAAn4nQMZ6emxGZCjh9mHxOC6d/uSNwaEQjPnxSBUTIAEkgAT8Q4CM9VRpPET7QOPCrG4fnlDgcuBPeISEQpW6X9GrglevZkCt1Yr46Pnxj1qxViSABJCABwIQ6wk3AI9OfyiFXOcVIQ1z6fSHr9gslk6vl4pEZZeuXKmphTc49sf+hwSQABIIUAIQ6zloNA4aDe7bJ+LzYdbXjdPfOvYnJgYq6uprmptOVVbqhoYw3j9AtY7NQgJIAAmcrW7q9sLpHy4kBv5cAX88pz9h/flCs8UMRp+kOmI28znEwmBaXjjrS0u1olBIIFgIQKwn8fK0wQPgCA8lNncD6z+e0x++5XM5ISEh2kHC+sNjAtwn+FzHeWDakMUdnr1SJc22RKaZOF6pEBNRQoD6Ld4uF684sWzd1dam1j6Vyx09bXIvzc6DYP/kzJSLN26cra52yePljRtburrOXK0Cz49ULH703nvW3pn/QHESJfCorgTH/lQTx/qQABLwIYGbKXlQ2tzElPvyCgqTUhMjouDwFnIfN/uLXOdFOP3DwuRtrS4bwOfy4HMmk6kfJqYQIPbfYBzmc2hrJHHs71U/pNlgmWbieKVCTEQJAeq71r5jlz4+XgExPyBfRmaavL7RjaACUWhcfOyuD953mUYqFm199DGI9TxQ9iVsAZQyK275vPmbVt6xMDuWEnhUVxJ01p/6J1OqVUp5fbEbNmT/6U+UV4sVBiIB6q0/UADT/9UV+VcVjRD1bw/F+WYQFhWpMQ4dKCtzyS4lLm796tXKvr59n36q1mjy09Ln5eZsW7sITn8MRNZTbhNtH2qmTAYLQAJIYGYQ4LJZK0qydzy78p+vP/Pacw9+95HFXRq1Sqc5V1FpGB6zT4NUKm7u6BhPKpF1+09w+CRERcObgqxMmEiAY35nBoWJtzLoxv4TRzSTcvhl5DWTAGFbp5lAgPTA3Kd/WtOisMkKc7nw/pV333Uv/cL8fNjWf0Cnk1ijg+Bq71Zuf2xpSjRxJAD9Lhz700+nKBESCHYCsRFhk0BAbvHWr9WReeHQR1mYFHd6mARJzIIEkAAS8A+B2PDJWP+EaBk0l8thQ5g/vOns6eFxwatE2yEybQXzT6fDWpEAEvATgTXb33zp3f073jmy/Y8Hu7odj3kxWyxFqSnOkaD2jU2KjoR/xkREkA8BiTEx8JfGY3/0+/upq05PtQHidZ0e4bDUGUDAjz3w7PXGvZ+ePnL+WmFaYmK47GZbb+dAX/eARtHft3XduuabLXq9gcNipSREGM2G6ta2kxW1DkBf3/LsoMHSo+5vIs70UukNQ9FCCZ/DgYnf7z26ZFlB9gxQwESaiNZ/IrQCPq0ff3sBzwYbSAUBynpgk3KgrUfbptKZRszzM6JzEsJt4r1/4vwHJy5ea25fPieXZea0dTnu+y8JFeRnJ//oySUV8taKhtZjF65VNLTALPFvv/fcodPlV2rkkaGSMKFQwOEZTEa4eSj6++HomJefemjH+jVUQKSqDrT+VJGmpB7KfnuUSIOVzDwC09oD5Z1qeedAm0oDdt8BTYJMdE9Bon1wjqK3f395xa5PPk+NicqOjzMMWuDox/yMeIFYMiczfnFunExCxHc6XHAP2F9+pblLVdNKhAzB84TeOBozKhUJ+w7smnkqGb/FaP3ppE3GtP72aEUKhZkeAtPaA1/Y/QmbFyHgubDapDQpMZIHipJjw4kjXGwXGPTdB8uOXqje8o1v5idFzM+MobErf0JaRes/IVyBnnhaf3uBLjy2LwAITGsPhGH4K385WNU6MD8nx42sOQkRsC+bNJTYtAcvNwTQ+tOqe0zrb49WpFCY6SFAQQ8Eb8zP//5Zce4c2IHZnRAj2h3fWjE9UtKkVLT+NFEkKQYFvz1a8UJhfE2Amh4IDwEv7vnEwgqXScfE9UOcvrytraG1tUnRmRITfmX3z3wtH63KQ+tPK3VS89ujFTIUxqcEqOyBh85W7/uqJiMhUdHbMzSojY8USgTshbPTUmJkKbFE5D5e7gmg9adVD6Hyt0crcCiMjwhgD/QRSCqKwbW+VFDGOpAAEkACgUYArX+gaQTbgwSQABKgggBafyooYx1IAAkggUAjgNY/0DSC7UECSAAJUEEArT8VlLEOJIAEkECgEUDrH2gaCbL2yHctYjI3HyGktr69fS3aJYcPj2y+9QmZyOGyy058Y0vtlNj2DVms/WWr12UNQaYPFDeICKD1DyJlB5yoYHczt52+1ayMreUW61W/s5TBKF23OgOs+ao9jE2HrR/tWeVoncdmJ+4eq/YQiQ9vYjgkvlUOFHN62/ox9l++a/02xs56IteeVc63hoBjhg1CAr4igNbfVySxnAkScLDdttyEOT5dunPv1gwGY+VuuBnsXjn6XXWd3bDdKbv80IenGZvWQuKxuYjMYz7Jz4KSb13WXNZPMmeXMk5fr5+gFJgcCcxYAmj9Z6zqaNBwGKjDOH3sdeTX8DSw6SXC9jvYaOvTgP3lnL20+lXSUeTSiXNkc+a2/MO37yZEWfXXbc8exD/H3GBoQBhFQALjE0DrP4N7x6De2NWrae7qu1zXdqqq8cvKho4Nz3c8/T1489XVxos1rfL2bkXvwMCgPhCFBEePbVR/u31H9u9hlO588dZw3/oFYbVvPQ3YUjplJ+z4acY68OFY3UQunDjwBFA/G24PM8u/76zlr6z6tdNyT+BqORB7HrZplADu9DDDuoJuyNiiVHf1aduUvUNG07CZYbGEGIZNw3D40IgZLhj4hoQwWSEhHDYLjqRmMi0hlhEhjzMrShodJkqJCw8L5QeQzDAba3XWj94IHP4JDSU9PLdTODwp3M5uTUgO7Z1LseUivqreWV9+69nidi67/H7nc1vLXb1DoFyvtRwfJY3yq5Zxpwe/dx7vG4DW33tW/kwJ9r1Zob7ZqWpXDRhMTO2gQdmtUvdr1X39g0NG8wgY/hFyyjSEyWQwmPAfCy42i8flSCTisDBxTHSERCxkM0fiZZK0uMiU2HABj+NPkci6xxpqJwts/brUpbV2yk5k/nAdGHbGrftA5uibF+vgxgBTu+Vb62/dLW59tdv2huFwX/ADGxpoGa2/H/rNZKtE6z9ZclTl0w0ZwKPT2KnWGkZ6+3XNzR0dnd1DQ3oWk8FmMeG8aS4M8kNCWGD1Q8DuWy8LPBBYzBYL3BTgBTbFNAL/ZPL4XJksPCl5VlRkGJ9tyYiLnJMWGyYa96QkKkQca/0dBu0OM7vEXWD1IdsI3/nmQd5LiM/JZwWHgb3Vwz96K7G7z9hqGe8BgwIOvtMyg8fn+VHLaP0p6C2+qgKtv69ITks5FXVtV5uUg8OM+obW+voWrVbHY4eIQ3kCPgesvQXsvPV/+DPeBU8C5H+QbHjYPGQYNphG+EJBcvKszIxkXog5PyVqfnZiSAjOAE2LBr0p1I2WwY9H3M0noGUG+ACdtTwnJWoeJVpG6++NxgMkDVr/AFGEYzN6+jRfXr3ZqzfLG9uvVNYO6w0REoFIyIURPlj7QYMRBvWkq8eWk4h1YTA5HPD2sO1vCJDOOGyCr2A+wHozYBiMpkH9MIfHy8pKycxMCuMylxemR0eIA5QFfZtFPy2j9Z9BvRWtfyAqq76l68uqZr05pOzLS11dquhwoTiUbzYTzhxorsE4XDInPTslDqZ5tYN6GLYTA0QLA0w8+Hh61dqq+lbbWB5uDyxWSGJsJFh8OBGJTAk3AdPICJRjHB7hCoT5c3MEbOaivPi8tPhAxEHTNtFSy2j9Z1BvResfcMqqqm8rq27WDpk+P3GBZRmJk0lguA6mn2woDPnB0f/bH30rIkzksuk/feuTM5dr42VhZAZwKK9dMf/bD99lSzxsGgFXAjw9wG2gWt760q5/yiLCiopnww1mcW58UU5ywBGhY4MILVc1a/V00zJa/xnUW9H6B5ayGtuUh87VDegMnx6/ECHihkuEMMC3byIM9lfcmb/lifvID+FpYPfHJz/69HxoKB9G9EMGY3mFfFFOckJ0OAzt2WwW3C3m56f1G4zgKhoxmUsLMh68qwBmicnsj/747YtV8rvy0yBuaG5hjkjIv39eek5qXGBBoV1raKxltP4zqLfiXF8AKUur0x87X2OyhBwvuyST8KVOph/cODDwX7EwHxp9tqqhuVMFTp9195YUZCSouvoMmkHToOFri+Zu+ubyH29+ODZKCguFICDoq0s1Hx4+c/x0tZDLKc5JBtMP8aFQQnVD+5Hyq/kpceAmYocwrlXVjVgYX1yR92uHAggK7ZoyeS1nTl7Leahl2nWkqQuE1n/qDH1WwhcXbnBCw8pOXRZwQsJEPBi2OxQNsRx5GQk5abPAq/Mfv/jLj3d9CAP8yDDRr154DNw7YgEvRiq+Z8Hsbz6wMCslbtv6leAd6lFrwiWhP9v08Jf/+5OX/2OtkM97fe/h7j4NlPyHj47zOWxYHASPFzA3wDCb6msbOELxZ2ev+0wkLMiJwIS0/F17Lf9gHC1LXWtZ2Xtby7DQz07Ljahl7JhAAK1/oHSDvn5tg6Kvrr55oF8TFR4KEfoOLSNXcq1aWgSfHzpV2dCq1A7o/t/fjoEviMNibXtq5cKiLPjq0/Krh8quwJukONkPv7N6/dolv3rxicdWl5oYll/95Ujew/9dLW+PjQzr6FZ/9PnFAriR3AoPYrPZULWyQ9HYpYZYlEDhQq92TFTLcm+0/LRrLcfJxtPyQJBr2WgauVzTuu/YpR3vHKto7KZXF5uANOj3nwCsaU1aXlF/rVN77LMzTJNRJOSR4T32F8TzxMqkP3r24cHh4ad+9q6qp39eRoKyd+CF76y+b9Fc0hf0kzc+mBUp0RtNmx+75947CQcReb39ycldfzvW1tVbnJn4wa+fB6fQq//771/96fAjdxXA5hC2mmBqIDJSmleQf+z9qpbrAbk70Pg62LRpHrymVUdTL3z6tfxpW5fKSy1nRwuXzc+eulD2JQSs379PM1gl76xrUVbD2sl2FdnmgtxUnkT60roS30KYKaWh9Q8UTe09eEapHT5x8oIszPXiWxikC3hc2PXleouiu1eTlxyTnx6/amnxsjtyYb73xs2Op1/5k6q3f9ncdNglBj5Zt/rOpPio89U333z/8+sNbXfkJGclxoiFvMzUWRWNHYfKr86Oj8qYJYNxkA0B3Am4XM7coryGa51n/t0XKGi8a0dKivSf/1znXVq/paJUyzc7Dp1yp2UBc2TzI0t9yyKgrH+bUl0l76hr6a5q6CD9YCqtpkc7oNbpFqQTD8orFheHhYVuuDu3Qt569EJ1WWXdM2tWi/jcWKkgQSaSSfy6DN63inFVGlr/6WfsXQ2/+PNRiP+rrKiRhYfaDcfHZIYbAGzaAOH68THhyxfkrVpSCFb+ZkfP7/5+7O+Hzoj5nAWzU2Fql1wGBveAOusen2uWFD6yYt6Js9VK1QDkhUndrj4NxHfGRUis0f+OVeTkpptMppeffdC7hvs51b//XfvKK2VcLuv3v185b94sP7fGU/XTr+VrSlW/H7Xsd+sPhr62SQljfHgDPwHY/LBvUAdGH159Oi2pn0ixuDQ9B94surNI0dP1/hcnFb395FdvvvD8b/Z9nBAVHR8dlRIbm5MUnRMfvjA71pNiZ+T36PcPFLXBGBxWYEGMpqM9tmsg2HoiAYPx+OpFq5cWtXerv/+7ffOeePmv/y4vyUq8d15OKG/U9MP8MIvFfPSe+cfffvG9n2+ExIkJ0SrNIJQgFQvyUuOSosKdTT+UDLcN8P/AsuBA4eK2HZcudfziF6cgyQsv3Bn4ph/aOf1ajqKflr3vijDA3/6Hg+8cKN9ffuW8XF5We+1o1ZVzDXXyrk6b6SesfyixrN36JG3ef+q8zfTDhxAznRoZY9YPyxubj5w89eq7/zhbq/C+ATMrJY79A0Vf//X7j3k83vVrcpGA57BzDxnIPzxsgmBNoYAHN4mk2Ei2gPfbf3wGq3WXzk2PCRdz2Wzr3m7EujCjyZSXnrB6WRFE+oN4lXUtr7138P/KKvKSY/OTY91sCkRYf7MlZ3b6oN7w2ncfDhQ047Sjo0Pz5JMfazTGJ56Y84Mf3BngrSWbN1EtswS8380oLft37P/FhdqfvXvwtLzGfWcozciJFInzspJYoZLX//IX+EHZ0v9841M1da2hoUJhqBD+cjgcaShv25rCGdG7JtrImTHEm6hUMzG9oqcfFltB4I3zpm16gzE/MxHidh5aXgw3gBBmSG2TouzctSfuLTnz3vZ9v9ry9NeWWbd5GN3kecPaJS8//wiY/us3O575+XsLN7x65FTl8oLMpYVZYpEAVgCMdwMgtoVgswQCrgK8B4F9gdH/z/88An9LSxNniukHohPV8lfBreWJ9sFz1c3g1veYK1wYSjyHMULaupX2ph8+1Or1ndr+mwpFfVNLReX1y5eveixt5iZA6x8oupO3KsShAlGoABwv9m2CsTx4e77z9WWPrrxzJCTky8p6cvnuH17a8L87nimZkxYdGXbvojnfXrtEb4DHgxEBn7t8YR5Zwrv7v3zvw+MLcpLvn5fzzNq7fvXi4xu+vgyc/jC761Js+Dw0VCAU8OUtnYHCZZx2bN/+RVOTOisr8je/GV32HOANJps3US2/Na1abmqHobpvX37UAhnHqehXu2+DRCAkd8EyMZjNHY79vFvdv6S4eNG8opLCOSXzC4qL50pFPD8KNa1Vo/WfVrwTKLy+RTGgHUyIj3YYmA/pjWDrE2Ij2pV92//4yaLi7Fe+943t/7F2TlbS4fKrL/7u/WuNHVDNyrsKYDMfGNcPaIf+9HEZ+QCxY9Pa/37mwSgRbBInuKskl81i/fXImcv1rTwOe7yWRUdHwGLUuuaA9nWCr//06dbISCGYfpjvnQBlfyf1Tsv/Qi1PQlG1zV3qwaGBoUH3eWUiwukPRx+FSyQ1zc0OiYf0hklUPUOzoN8/UBTHWbBx1eKiVcuKq67W9/b2k7O7YMRhOP+zLY/MTo8/c7VB1a+FXXrg80/PVr++98iJCzciQ/kP3DnnVy88Hhclhc+/++qfu5R9FrN51bKiZ79xN3wCB8Js++VfC7ISX3h6dZuyr/Dxl4vSZiVFSYedFhJDnI9UKimYm3X8XPUnx88Pn303UNCMbcfJk00//OGngdk2j626wjz34JJiGmvZj37/dw+ceWv/iYqWm+61UJKaERsWLpGKEpISwenvkHjVnQtK8ubYf5gSI4GQUI+aHTeBq3OEyMTkQUO2E4lGjyRyKMjhuDuH84sca7Wmtp5il2H31XjnF+HYf/Jq9W1ODodddul6S0dPRmYSn88l/T/k1g5g+uH9nXPTwfQfO1u9bNPr9z/7y8vXbj5QkrP2rkKD3vD2+58dPnV18bO/gKE9rGqBuYEPD599a99nMFUcERb6k80Pr7mbWAb1zv4vjcbhBFmYw85x8BVUx+NxMzKSwOMP1h+eIXwrnW9Lm1njfXvZJ67lRp9rOX2GaHmifQbcPirtaEynm7ww30uMq9icpk4X7k1Nv66zowteik4l+QZOUJ1oS26nH3s6XcbWcvJMjvqdpWD8163OgNPo4DA6OFSO+GjPqs1HxlblcLgd/NN6CLbl8CaGc2LiDOz126wH2I25iE/hhkDk2rNq0S757S9x7D95zfo2Z9iyLbpBQ2p81H88eh9M4N643gCWGrZj+/HmtYW3dl0Gb85nZ6thc7eUuEgxn3vxauPpynp4SoD+1N7T36zsXTwnY1FRpjXyxzykH37o7uLM5NFQZbVmcO7jLydFSHISo+1XeJGmn81h5+SkQpDDno8+v3GzXSjgasr+6FsBsTQgQHst+2vsD7GeG1/b9+m1Cpj8ctPTRHz+8hxiaJ+ckXKx5sbZ6mqHxMuKi5cWFzt8uOPxBZPpvXa2e8yhoS4H6M4fOmV3OvXa6Vax/no+Y8+esWN/h+NN861nnpIXjv0no9bpyAOBnjDkh71//3LgJJfHyctL53A5cOguDMMv1TSfv37zwvWb4I4vyEr62t3z5s1OzUyNP3qpprtfS2zXzGQmRocvyE7esPauTetWbH50xXcfv/cHT6+ymX5o8Aefne9W9cN57sNjZ5Vh+RiM+nNz0sQS0T8Onbre2A5x0NYTwPDyPYFxtcxCLU+J9qWaVojod2/6oQI4Jwn+gtM/Uhrmcuw/pUY4ZwarD+P0sdeRX8MAfdNLY3wzDPmhD0+TTwP2l3P20upXiTP8mEznx4T12/JfenG2Uxvqr495HKiuuz34x7G/j9U96eK+/uJbn12os4wYIdY+NzX+2w8tCRXwGhpaGpoVVc2dMOofNpkFMF3LgnhNC1hn+KRfMwj7OsCHtrXBaYkx11u6egZ04WGhcM47cby7dfIgQhL62blrUSIBbOtmGB6NKYLnA8gYHi7JzEqGYyLB9F+puQmbgDJCWNmJ0Zf+9rNJy4IZxyMQGFpuEvK506Rlf439/+dPn+79vLxOQURAuLkKk1ITI2RcIT8jI9XZ6Q8ZH166NDEqethsAgPL43DhkwRZ+A+/5vg0MIEebvXU2439iX9XO3jmrWnIaQDHku2y307FcHTvEwP8D9fVl68+5OT3v12A0+MFjv0noMdpTXrPHbnwg4QqwP7Cpj1v/uNofasiNze9aG7WiqLs0uzkZflpCzITi9NmzU+PL0iJzU+MvrcoSwD7OtzapQ2G6zca2tR9A2aDsaujp6Otu6Oju7O9u6Wl62KlPDFCkpUw6vMBq08O+WGOYe7crM4e9Vv7joLpF4DpZzCYLM7XlhM7ieLlcwKBoWXCqNFJy2Ssp0rneWPaSJEEZOcJBW4G/oMGvSxMevLi5Us3bkhFIjZsfu7D68j+PQxGfpadlbd688H378L0j603czbMFlizZmTBDo6nr9ff+t765HB6WyYzk/D7wxs797411+3LvmqfCuZDRsFX1JqlRSFmIxy9br0BcLt6+9/56IuPjp3hC/ng6AEzLRIJYRd+GNDDHxjvS4R8+Kf9VqAwzOdxOfEyKbh3shNjiFcC8XdO2qyizITMWVFwexgmphLMfD4vJTW+uDhXEiY+WHbpD/uOtShUcNexunuYjBHT2mVTGOwEn+68lxi17D0r71NCrKfOYFBpPFh/Hocj5BJ3vghpWLOrKV/4SiQQwLRZdWNjTXPTqcpKvdEIzlfvW+IxpbwOZho2rb3leWfAtC9YbBj225zxcDNw9utYy81Yva6UYfXc2EoZTewwnWx9iLhVjjXXnv0wn0z4gEpnZ95uI1p/j/qiKAGcxfjovfOZ7NGlJWDH4dnz+PnqN/5y6OipChitzC3Izs/PnDUrBmw3jPdhZRdchO/GetlaCdYdQnrAuQ/7PcALPKGwVFivN8Ib2L8zLk6Wl5dRVJQbFh5Wdqnmjb8eOlR2GUrhc0e7ODRgSWE67B5KkdhBVg21Ws4JEi3DEl+Pi7ygo4ULiaOw3Tv9+Vyu2WJW37qRwP4qvh37O3jh5btehUcB67CduMZE5Dj/NMDIH84nksINY9Nh2/3Cw28oY+teIp4IzgYBB9SYBwz0+weQ+YETVxY+/T+9Wj1j5HbcAqxENxpNcEpXduqsvIzEtISYUD53aMig0eh0g0ODg0MGw7AZonaI02DGLBSDBY3Q0blctlAogBW8IjHsXCLQD5uaO7qvyVtrGtvhbADo2RCDeBsBk8Vmh5x8+8XCrMQA4kKvpkxIy1qtTqubSVr2i98fon2OXq5sV4/u2j9ef8mLT0qLioH19KlpSS6d/pBx66OPqbWayrr6ivo6+OeWb3xzWUHaN0qd3PG06JNo/QNLjQdPVT6+/X8tFjPDMma/B9jdAXbyAb9PpFScmRSbnhQbHx0hgVNgOMRUAUSIwjMAPAeQqwSsdh/+hMCuQcwQJnhF4fyvDmVfY1tXXVNHl6of7iiwKxy5oMzO9IcwWdw3tq7duNbHe74HFuIAaM2olhlmhpluWiatP5XXgCTynY3/U9HS1Nrr4ZSupdl5sM1DWFTkMGPkg88+c9nIH61fr+jtraipBesvEgqffOD+B+bnrF1A7JZIvwutf8Dp9L3/O7X1jY8sYBfGmgZoKIztwb6D7x5+YjBDKwkVwGKuMHEobOQQJgKX5ugOcfAICfYdtnzQ6IYgLghOE+zXDuqGDBDYAzFDMN6HBI5iw12CzXvxieU/2/hQwBGhY4PoqmXqrf/l4hUnlhGn+gwajd2a/j54JB7WDwwOwc7+9h2Hw2I/MIeIZUjOTLl4w0WkP5n45Y0bW7q6Tly62NTRKRWLv75s+ePLC+8poOejMFr/QDQtfzl0etsbHw7Dlp0m43jtIwf71jG/BWZqnUdchBsIvmEy4TmAMPjEhPE4F4sDX/30Oyv/66nbs1GByIVebaKllqn3/Ox45wgE/HjZNcAXWlCU/9Y/P+pRu9jFVioWgeeno6fns/PnwPonREffu2DBUyuKl81J8LL8mZUMZ30DUV9PrS7992+2wLlCTI6AjAJyvsCYg+sGZmthU09Yn8V3esGH8BUfAh1gtD+e6YebA0cwSxb21x1Po+mnuCugln0CPDspmjvOxiTJqUkOVcBPQq3RujT9kBIW08Bf+K1AGuKfbBZ4XEV8X8b8+ERkXxWC1t9XJH1czqLCzM/f/P6Wry8G283k8Me7B0y+VuKcMKLYJ+8rPvGHH6xdhgH+k2c56Zw2LXM57GnV8hP3FtFVy4/fP+8fr23Y+tjS4hxH/wzsXOJs/ZsU4+5eLhIQ6fmc0bg7aagYXKS+jfmZdFeZjozo+ZkOqr4s82p92+8/PL7/5GX9CBNmAixmEyzenXwF0J1DYCqYxTQPP7i44D8fvXvhnPTJl4Y5fUSANlqm3vNjrwEIcIDY/7Yu9Y4/H2Rb106KrE+/tjTg9D9VUVFRb1snNUZ/hZmZsNZ3QKf77Nx5iPd/8oEHIJb6p08sz4iT+kjPgVUMWv/A0sd4rYEdfv7vy4qDp65evNFErAkgwvxH4BhG7+4EYPEJxz+sJbaMDOcmx6xePBeOei++tXnczEAQBK2kgZb9a/1tfST36Z/WtNw+owLmcuGrV971sGn54oKCFSUlYP0locThX3C1d3dvf2xJSjSxQph+F1r/GabTirqWT89eP3Gxpr61S9GrgVE8sTrXNulLxokybaGc1m1+zOZIiSAtXra4MHNl6Zw78tJo/DA7w9Q5TnOnoOWoxYUZftRygFj/5S/85mRFrY2ul9b/npL5iwoKW5XKxOhoyAsLfWEmYNuaIplEQI9+5SAFWv+ZqtZBg7GxDXbt1FbWtfaoR3c3PPf2n4ZD2KUbvwVjfQ6LJRULYU/Q6HBxarwMwkNnqqhB3G6XWpa3KuEUz9lpswJQy360/mu2vzk3NYFt4cGBSPvPXbre2uZg/d/75F+K/n6HSFD7zrV+9QMpcQl9Axo43BT8RS1diqSYWDjSHQ52p2UfROtPK7X68bdHK44ozGQJ+LEHnr3euPfT00fOXytMS4Tt/s/U3t7KGMb+4CqtuHQVgqTb1X0dfSqlxkXE50/WP8mBKLux10vrSuj6rIzWf7LdPCDz+fG3F5A8sFFUEwiEHvj+ifMfnLhY3dSWHCVr7+5vUCi3rlvXfLNFf+vMXoijk4SL958pV2vHHAL8+pZnBw0WiAdtbm/r02gsJlMolwvHZ4hF/C1fX7KsIJtqmtNcH1r/aQZMbfGB8NujVmKsLbAIUNMDtfphRZ+urUfbptLBgsfCFFl+cqTDCF3R27+/vGLXJ5+nxkRlx8cZBi1tSnV+RrxALJmTGb84Nw68+U0K1cnK2nM3GuEvzBL/9nvPHTpdfvxSpUumLz/10I71awIL99Rag9Z/avwCLDc1v70AExqbE0AEprUHfv/tAzy+hM8D54zjunVYk7UwO3Z+Rgyf63gkNZj13QfLjl6ohi3b8pMi5mfGuFnABYn3l19p7lLVtBIhQ+BN0htHt1yUioR9B3YFEOspNwWt/5QRBlIB0/rbCyRBsS0BSmBae2B7T9/3dx/MSk4nF+U6XzD8hxvAwuwYus7T+lbraP19y9PPpU3rb8/PsmH1M4HAdPdAGIlvfmMfhx8Om/C44REr5d6VG5+X4i7NTMA5vW3EnR6mly+WjgSQgA8JwMZWe//7qfwEwRcXLkA8vnPJsEUPnMy19+iX9/zX73xYLy2LwrE/rdQ63SMvWsFCYaaBAGU9ELzzW37/wfJ5JRkJCQpVL5zT267sautWgvWPjQjLSYpdOjeLZpO0PlcXWn+fI/VngZT99vwpJNYdwASo7IEwQ/u1l9+Cvwtnp+UkxsER1vCmMD0RpmcDmFAANQ2tfwApY+pNofK3N/XWYgn0I4A9cAbpFP3+M0hZ2FQkgASQgM8IoPX3GUosCAkgASQwgwig9Z9BysKmIgEkgAR8RgCtv89QYkFIAAkggRlEAK3/DFIWNhUJIAEk4DMCaP19hhILQgJIAAnMIAJo/WeQsrCpSAAJIAGfEUDr7zOUWBASQAJIYAYRQOs/g5SFTUUCSAAJ+IwAWn+focSCkAASQAIziABa/xmkLGwqEkACSMBnBND6+wwlFoQEkAASmEEE0PrPIGVhU5EAEkACPiMQdHt8knsQ4uVDArEbNmT/6U8+LBCLmrkEcI/PGaQ7HPvPIGVhU5EAEkACPiMQdGP/yZGj2YiGZuJMTqeYazoIYNeaDqrTVCaO/acJLBaLBJAAEghoAmj9A1o92DgkgAR8TsBoGrlc07rv2KUd7xypaOz2efkzpUD0/HilKZo9z9JMHK9UiIkoIRCwXatPM1gl76xrUVY3dDS2q0gYc3OSRRGyHz0yjxI2AVcJWn+vVBKwfdqr1jslopk4k4OAuaaDQEB1rTalukreUdfSXdXQoezVgLx9Oq1Kq+kb1JWkZsA/77urSCQRbbg7dzpQBH6ZaP290lFA9WmvWuw2Ec3EmToQLMFXBPzetcDQ1zYpYYwPb3RDRpvFB6Pfo9OYzWb4JFIsLk3PgTf3331HXpJs2ZyECnnr0QvVZZV1z6xZLeJzY6WCBJlIJhH4CktgloPW3yu9+L1Pe9VKrxPRTByv5caE007Av10LBvgbX9vn0uLbS54RE5cbl8DjsOcU5XcpWz/58oyit59M8OYLzx8+fm6EaQlhswwjptRZso0rFy/Mjp12cP6oAGd9/UEd60QCSGAaCFyqadXq9YeuXjpVf+NGZ5tS008O9h2ucGEofJKdHm82W947fMJm+uHDIYNxiDkyYrEwzQz+SIiyve9srWIaWhoQRaL1Dwg1YCOQABKYOoErteNafPvCI0Vi+GdkuESh6tUbDfZfDen1TIMJXCLCUH5mVmpx8dyptypgS0DrH7CqwYYhASQwAQJkHGf3wKgPZ7ycQi6Pw2LDt91aQ1Nnp0MyeHTISE8TCATqvoGq6trTZy9OoAUzLSla/5mmMWwvEkACrgjUNnfpjcMwteseT6RIMpoghN3sZP271f1x0bKivNxFd8y7q7SkdOF8qYhHV95o/emqWZQLCQQXgXPVzbaoHjeSSwREJE9MdDiXw3Ee+w/pxziC6E0QrT+99YvSIYFgIQBuHwjn9yitzOr0N1oszk5/+HxAN+SxBNokQOtPG1WiIEggeAlArCes7erWDLhHAB5/iUAIaZhsrvPAHz7X9Os6O7rgpehUkm+6VWq6YkXrT1fNolxIIIgIQKzn8IjJ49hfIhxdwBUbJXN2+gMvFp8bNysGXrFx0eQbnYm2RpK2ggVRx0dRkUDQEyBjPT1iiAwl3D4mppnPdeH095idZgnQ+tNMoSgOEgg6AmSsp0rjIdoHuIRZ3T4hHI5Lpz+RQCxWqfsVvSp49WoG1FqtiM+hK1C0/nTVLMqFBIKFAMR6wg3Ao9MfcJDrvMQSsUunP8lLp9fLwqRll65cqamVikRsFm2NJG0FC5aOj3IigaAnALGeg0bj4NhVu85URHw+uc4rVhbZruxyiU1kjQetbmysaW46VVmpGxoSCXDsH/Q9DAEgASQQmATOVjd1e+H0DxcSA38zkxkmErV1K13KwudyzRaz+pYTacRsxrF/YCodW4UEkECwE4BYT+LlaYMHwBQeSmzuxuJx1BotvMYZ+wtDQkL6b32rNxpp7PfHHZ69+vH4d99ar5o4kUQ0E2ciomPa6SVAdi0qr8vFK04sW3e1tam1T+VyR09bY5Zm50GwP08qGraYD5SVuWzkj9av7+rtBY9/RX2dSCh88oH7H5ifs3ZBGpUSUVYX+v0pQ40VIQEk4HsCN1PyoNC5iSn35RUUJqUmRkTB4S2kf9/+sq3zioqMaO7oGK8dPA6HyWSqrZsFsVksg3EYx/6+19nMKpFmg2WaiTOz+hK9W0t914LD2T8+XgExPwA2IzNNXt/ohrBxxLxwQdGuD9536fmRikVbH30MYj2PnT3b1NGZEB1974IFT60ohsO/aKk1HPvTUq0oFBIIFgKP3z/vH69t2PrY0uKcRAfTDzcDBwohPLYbpz8M9iF9CDOEvDew2SxwJdF47I/WP1h+JCgnEqArAS6btaIke8ezK//5+jOvPffgdx9ZvGbJnDkZswQWfbiYWN5lu0JFwiaF457+tm9F1rVgEPZDfiINFYP1x5gfunYblAsJIAH6EIDbwJz0WStLZ//0bx//zz8PwOsvX5X9u+JC8fwCeMGbyPBwN05/WNtFskiIiobngIKsTB6Xg/H+9OkfKAkSQAK0JxAbcesIl7Gi7v7XJxX19eOJD0E+8FW/VvfI3cu3P/00+P1h0S+O/WnfW1BAJIAE6EMgNjxsEsLA1m+Qi8thQ5g/vOns6eFxuej3nwRJzIIEkAASoI7Amu1vvvTu/h3vHNn+x4MdShcb/SdGRDpHgtq3Lz5aBv+MiYggXf+JMTHwl8Zjf1zt5VXvpD6OzatmTTYRzcSZLAbM53sCfuxaZ6837v309JHz1wrTEhPDZRX1bSqtBl5w3OOLTzzZ2tym0WhhFrdd3dfRp3K5HfRP1j/J4ZD7/DR0qXrbe7pjQ8PChYJQIfdXW9amxEb6npdfS0Tr7xV+P/Zpr9o3wUQ0E2eC0mPyaSQQCF3r/RPnPzhx8Vpz+/I5uSwzp63Lcd9/DpslCRfvP1Ou1g7as3h9y7NXG1pPnL8gZHPDhEIBh2cwGRX9fYr+fhaLeeSXW5cVZE8jO8qLRuvvFfJA6NNeNdS7RDQTxzuhMRUVBKjpWlr9sKJP19ajbVPpTGZzYYosPznSwUWj6O3fX16x65PPU2OisuPjDIMWOPoxPyNeIJbMyYxfnBsnkwiaFKqTlbXnbjTC35oWxW+/99xHx0+cvXbDJamUWNnNv/+CCohU1YHW3yvS1PRpr5rii0Q0E8cXSLAM3xCoXL4cCio4ccI3xY0t5ftvH+DxJXweOGccdxOCudmF2bHzM2L4XGLFlv0FZn33wbKjF6q3fOOb+UkR8zNj3E/kQnpIXNuqqGlVkOWcrKiFv1KRsO/ArumQy19lovX3ijzNzCXNxPFKhZho5hNo7+n7/u6DWcnp5KJc5wuG/3ADWJgdIw3lzXxxp10CtP5eIbY3l1UNHdXyztoW5doV8wvTorzKH2CJvuBw2xKyuG/vndFSBBhUbA4VBPTG4c1v7OPwwyEY3019sVLuXbnxeSnu0lDR3MCuA62/V/r5W1J2W0K25tubquSjuwPOzUkWRch+9Mg8r/IHQKI+zWCVvLOuRVnd0NHYriJbNOOkCACQ2AT/E9j58ecfl9csKiiw7cpgaxNs0QMnt7QrlXB2Y+eHv/R/WwO4BWj9XSsHtgyslndUN3aCxYRTQ50T3XdXkUgi2nB3bgArlwHTXHC7qmvphucVOAFjhkoRyISxbf4isL/8ypbff7B8XklGQgIc0Q62Hg5rBLsP1j82IiwnKXbp3Kwd69f4q3kzot7xrb9816LMbfmHLbtXMqxvT9vkKd1ZX74148hm5qo91s82WRM5XHbZiW9sqZ0SuynHVq/LGnzO173FJwKHtQNqnW5BehZUfe/yO+Yky2DrVwgbgDmiA+UVGx5cGSbkx0oFCTIRhBP4vHleFgj3KnKMDxZfN0QsWbS/HKS4/+478pIIKSrkrSBFWWXdM2tWi/hcv0vhpbCYLJgJwPTs115+C/4unJ2WkxiXnRgDbwrTE2F6NpixeC/7ONb/lt11MLvWjxmE8a8nbD/xbebtm8TtWh2y2+4EjFu5bDcLq+13XQ6R68N1o3VVW+843ss14ZRgKB9/6c/22YZHTH2DOnLBSJ9u9Bw4ODiiND0nhMmcf0ehydB74NQ5sJtkrp3bnvvXl2fhDTyNGoeH02bJNq5cDHEIE27KFDJs+93HNq8OWYwbKXgc9pyi/C5l6ydfnoHwODL9my88/9EJ4k7vRymmAACzIgEk4C0BVzs8Owz1bUXJd63fdrp0517CCq/cbbHcHvFX18mdTf/tfIc+PM3YtBYs/thcRILxy5ETufKzoK7M2aWM09fH3ZrJW1Hdp7tUQxhxw/AwLO641t5SVnvtaNWVcw118q5Om+mHBJGhxMHQGalxphHz6//4t830W/MaeZYQeLFMZo6Z2d2jPVs7GjHmmyZ6KgU8+6Tp91KK7PR4s9ny3uETNtMPeYcMfpbCk5T4PRJAAr4hMM7+/jAaP7zJoYYjvwbvz6aXxo7ArTa6dN3qscNy5+yl1a/CgWlwbT7isuHO5dRfv+1rgixjbjC+kX1MKVdqiXXhn16ruHBT3tjdNTA0ZhGgLalMROwdGB8jM5mMeqPBvgiDwWgyGi0WszBUkJuZVpRH9ZTAZesNzHspIsMl4DB1kGJIr/evFNOgWywSCSABFwRcWf+MreXOfnzGkf17GKU7Xxzj4D+yGeYDRp8GbIU7ZSfs+GnGunqLpX5n6Z5Vi3bZPSiQuVyWQ62+wEsObn2PdYYLQyENm8urqG9wSKzV6zPS0wQCgbpvoKq69vTZix5L820CuIFBgd5L0a01wFxZoEnhWyZYGhJAAuMR8PpsL8L4k46YWxc4iGDad9Nhj/54wnNDZs3Iymc4OXHGKcea6/Y1pmpf6xMcJhASo+hXuy9YIhCGhBDEegeNDW3tDom71f1x0TIY8i+6Y95dpSWlC+dLRdQtOYEp67NVTdAkL6WAczAYIexmJ+vvXyl8rVgsDwkggXEJeGv95XXVENxD+O5Hr9Hher3tKQGM+Dh+nYzV60pJz42tlNuJxy3HmmvPfnAUEc8OpbMzp1GLEAI/aDSO5+2xVSwTEU7/+FiZxcJwHjX3DeimsYmeioZQH7gBeC9FdLSUy+E4SzGkH+PO8lQtfo8EkMBMJeCt9Xfwwst3vUpEe57elml15rtw5tgDAV/Q4XwiKfiJNh229yq5Kydj617CUcS0BgV5fMCYkgLOXWtWeeH2ibRaf5kszNldDp9DnM+UGjG1zKTbx3spdMMjLqUY0A1NrSGYGwkggZlBAFd7MWDI/MT2P59vbGjt7XavtAfmFMHpEDmzM642Nh07e8Yh8V2z83NTUuFDuB1CQBS8yUiKevGbd1LTEchYz4qWJi+lkMbKlGp1oElBDSusBQkgASDg7difxrBIn4lSo3Yvo4jPJw8GgilfZ3c5fM7ic+NmxcArNi6afKMzUYQXJi3IWE/vpZgVHRVoUtC4j6FoSCAACVBkngJQcluTzlU3Q0Q/xMi7b2S4kHD7hIbyuWy2s7vcvwKerW6CBngvhYlphsVcgSaFfxli7Ugg2Aig9WfAlK/LY94cukKkSASf8AR8l+5y+CpMLFap+xW9Knj1agbUWi1l50FXNxCBm95LEcLhuJGitUsJInSr1SAC7KNOmRTB9ttDeZGAfwkEu/UnfSYqnYsd0JysP7HOy+J24K/T62Vh0rJLV67U1EpFImrOgwa31eg6L6+lEEvEbgb+egMhxfELFy/duMGw0PlUa//+9rB2JOBfAsFu/WGRF+yEo9J4sP48DkfI5YKqIsOlsJWgS52JBOR50I01zU2nKit1Q0MiAYcC7ZLzFhOSIlYW6UaKEbPFJgW0nxopKACFVSABJGBPINitPwRKelweBbzChYTbh8PlSEJDYRdZl30IPOlmi1l960YyYjZTM/Y/VdEI7fFeCqNlJEwkciMF7P5mkwJKpkYK/FkiASRAMYFgt/6wuVv3gOcNHshIfw6PC7uHw2ucsT+xErj/1rd6o5Eajzm5P533UrA9SQEb2NmkIMb+fCqeYCju91gdEkACQW39wWcCGzuTmze4v8hVviwet0nhuDGOLaOAf3tfB5FQCM8BFIyaYd6CPLbFeylg4tq9FDwe4eMi7L5QCLO+FEjhCT9+jwSQgO8JeDZ8vq8zYEq8XEOsjy1MSlkxu2BuYkpiRBRs308G9dtf8Ans8AOfREjDmjtGT3Z0FgLmBmCdl9o69QqnThuMwxSMmslYzwlJER0Z6V6KEfOITQqY9aVAioDpEdgQJBBEBILa+sPBjaSqYUZ3xcIFcBuAk1tgQe9DhSX2L/gE0rBYrEhp2HijZqnYOjEAW6dZL5gBhjsBO4Q53V3psnWDB++lgI2AYiLC3Ush4I0+xJDz2BRIMd2UsHwkgAScCQS19V9ckBYuHj0ETl5PzJ3artS05MycdPtPuAK+G6c/DPYhcQgzhJwVYLNZZrOZglHz3fOzoiMIrxR5OUiRkZnmoHIWn+NRCuIJ5pYUxJ0M/f5oOZAAHQkEtfVfWTp7745v/fBbK5YUpYcKRp3dpJZvNjbX14zZwR+svxt3ucjqGoKwHzK7NFQM1p8Cjzm0/N3tj//k6ftAFvvbgMubAWHKxSKPUgi4o2N/kIK4k7GCupPQ8VePMiEBggD+sBlgQOEGsO/VDb/+z4e/+8jiLo0aFn/By2HvB6lU7MZdDmu7yA6VEBUNzwEFWZk8LoeySPmF+SnQcrgN/OG/14Esj983r1+vAxGc96x27/R3loK4YVCyagF/jkgACVBMAPf4dASe+/RPa1pun8f78saNkOKVd991r5jFBQUrSkpgj4TEmGhIaRohrs0rC1KiiRXC1F+pT/64SdFD1kuK4L0ULV1dSTExkB72KYWJiw0rcv0lBfXcsEYkEDwEcOzvqOvYiLBJqJ/0+3M5bAjzhzedPT08LtePHvOU2MhJSMHnEqH9TAaTlIKcs/ajFJMQAbMgASTgJQG0/gSoNdvffOnd/TveObL9jweVKhdHdCVGRDpHgtojjo+WwT9jIiJI13+idexMscfcXoqWTrVDDzAYjV5KAY8vtgkM6qXwsuNiMiSABKZIgLVjx44pFkGD7KlxsjM3Gv5ZfhFi/UdM5jZVr02oZcXFZjiqZdCYER0r5PFhLldndHH24X0l87jWydLqxoYqecOpq5VdncrDp6oPnLpaMjtZKhqNLJpWVvZSmEfM7b19ZHUgAvw1DZtYhpG0qJhQnmA8KVaXLmCxiOG/H6WYVkRYOBJAAjYC6Pcf0xneP3H+gxMXq5vakqNk7d39DQrl1nXrmm+26G+ddgu+kcjo8LM1VyvkxP4Ktuvnm56ubek8cf6CkM0NEwoFHJ7BZFT09yn6+1ks5pFfbl1WkE1ZtyOlqGholQqFde1d2x57rEvRrerphakIsg2wLkESLt5/plytHbRv1etbnr3a0BogUlCGCytCAsFJIOisv1Y/rOjTtfVo21Q6k9lcmCLLT450cNEoevv3l1fs+uTz1Jio7Pg4w6ClTanOz4gXiCVzMuMX58bJJAK9cfhkZe3Z643HLl6Dv68/t/lfZV+evXbDZTdKiZXd/PsvfNjDfCVFk0IFUpy70Qh/Ya77t9977qPjJyiTwodAsCgkgAQmSiBYrP/33z7A40v4PFi86rj+FmY1F2bHzs+I4XNHV+raIIJB3H2w7OiF6i3f+GZ+UsT8zBj3U6CQHhLXtipqWkejhk5W1EJp4PnpO7BrorpxTk8PKabOAUtAAkhg6gSCxfq39/R9f/fBrOR0MjjH+YLhP9wAFmbHSENvb9Y2db6+LYEeUviWCZaGBJDA5AgEi/UHOuCr2fzGPg4/PCGaCMkf74qVcu/Kjc9LcZdmcqx9koseUvgEBRaCBJDAVAgEkfUnMe38+POPy2sWFRTYBzWSX8HmNnDmSbtSCacedn74y6lgne689JBiuilh+UgACbghEHTWH1jsL7+y5fcfLJ9XkpGQAIebg62HYw7B7oP1h6VeOUmxS+dm7Vi/JsD7DT2kCHDI2DwkQGMCwWj9QZ0wPfu1l9+Cvwtnp+UkxmUnxsCbwvREagLzfdWf6CGFr2hgOUgACUyIQJBa/wkxwsRIAAkgAfoRwJ0e6KdTlAgJIAEk4JkAWn/PjDAFEkACSIB+BND600+nKBESQAJIwDMBtP6eGWEKJIAEkAD9CKD1p59OUSIkgASQgGcCaP09M8IUSAAJIAH6EUDrTz+dokRIAAkgAc8E0Pp7ZoQpkAASQAL0I4DWn346RYmQABJAAp4JoPX3zAhTIAEkgAToR+D/A/pALIiyVhkcAAAAAElFTkSuQmCC)\r\n子网划分的术语叫做VLSM(Variable Length Subnet Mask,可变长子网掩码),事实上是拿子网掩码变戏法。在上图中,我们有四个网段,需要四个IP地址段。而如果你只有一个B类地址(172.16.0.0/16)可用,通过子网划分,可以将这个B类地址划分成一个个小一点的子网。这样一来,一个庞大的广播域可以被分割成小的单元,另外IP地址的使用也更为科学更为合理。注意这里要通过路由器来分隔不同子网才能保证网络的实际隔离。如果你使用交换机来实现这点,即使你给连接到同一交换机上的不同主机配置了不同的网络地址,但是主机是可以自行修改IP的,这样主机仍可以自行轻松进入另一个子网环境。\r\n\r\n对于一台二层交换机来说,缺省时整机就是一个广播域、一个LAN。这意味着,只要连接到这个交换机的PC配置在一个IP子网内,即可直接进行互相访问,而且更重要的一点是,处于同一个广播域内的某个节点只要发送一个广播数据帧,在这个广播域内的所有节点都会收到这个数据帧,并且耗费资源来处理(即使它可能并不需要这个数据帧)。当这个广播域变得特别大(交换机上连接的用户数量特别多)时网络就非常有可能被大量的广播消耗掉大量资源。另一方面,实际的网络中经常存在这样的需求:连接在同一个交换机上的用户有可能是不同的业务部门,我希望对他们进行隔离,或者以独立的网络单元进行管理。基于上述需求,我们引入VLAN的概念,所谓VLAN,也即Virtual LAN,是一个虚拟的、逻辑的LAN,一组与位置无关的逻辑端口。通过VLAN技术,可以在交换机上,根据接口等信息进行VLAN的划定。不同VLAN之间必须通过路由器或三层交换机层才可以通信。\r\n![](data:image/png;base64,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)\r\n\r\n例如,一个拥有4端口的交换机,可以把其中的2个端口分到一个VLAN,另外2个端口分到另一个VLAN,VLAN之间互相看不到对方流量。这样逻辑上就相当于有了两个独立的交换机。通常情况下,交换机会将流量泛洪到其他所有端口,VLAN可以将流量进行隔离。理论上,如果VLAN间没有互通诉求,VLAN之间的网络地址是可以重叠的。但是正常组网下,VLAN和子网应该是一对一的关系。VLAN作用仅仅是用来将连接到同交换机的子网进行隔离的。\r\n\r\nVLAN是在数据链路层进行划分的,不具备位置属性,可以在一个交换机广播域划分,也可以跨交换机划分VLAN,那么mac帧就会携带VLAN号码标记。VLAN是不能跨三层路由器配置的。子网是在三层进行划分的,不同的子网具有位置属性,通过路由器物理进行分割。\r\n# 3. 交换机高级转发决策\r\n在[[交换机原理]]中,我们学习来交换机是通过MAC地址表进行转发的。引入VLAN之后,源地址表按照VLAN将端口与MAC地址相对应起来,从而使得交换机能够做出更多高级转发决策。\r\n# 4. VLAN作用\r\n- **安全性**:不同vlan之间数据隔离\r\n- **节约成本**:无需昂贵的网络升级,并且带宽及上行链路利用率更加有效\r\n- **性能提高**:划分多广播域,减少不必要的数据流造成的性能浪费\r\n# 5. VLAN格式(IEEE 802.1Q)\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402202302689.png)\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202401282157801.png)\r\nvlan id占用12bit,范围是0~4095,0和4095为协议保留值,所以vlan id的有效值范围是1~4094。\r\n# 6. VLAN端口\r\n- Access类型的端口:只能属于1 个VLAN,且只向该VLAN转发数据帧,一般用于连接计算机的端口\r\n- Trunk类型的端口:可以允许多个VLAN 通过,可以接收和发送多个VLAN 的报文,一般用于交换机之间连接的端口\r\n## 6.1. access口\r\nAccess口所属vlan可以分为静态vlan和动态vlan。\r\n\r\n|VLAN划分方式|原理|优点|缺点|适用场景|\r\n|---|---|---|---|---|\r\n|基于接口|根据交换机的接口来划分VLAN。

网络管理员预先给交换机的每个接口配置不同的PVID,当一个数据帧进入交换机时,如果没有带VLAN标签,该数据帧就会被打上接口指定PVID的Tag。然后数据帧将在指定PVID中传输。|定义成员简单。|成员移动需重新配置VLAN。|适用于任何大小但位置比较固定的网络。|\r\n|基于MAC地址|根据数据帧的源MAC地址来划分VLAN。

网络管理员预先配置MAC地址和VLAN ID映射关系表,当交换机收到的是Untagged帧时,就依据该表给数据帧添加指定VLAN的Tag。然后数据帧将在指定VLAN中传输。|当用户的物理位置发生改变,不需要重新配置VLAN,提高了用户的安全性和接入的灵活性。|需要预先定义网络中所有成员。|适用于位置经常移动但网卡不经常更换的小型网络,如移动PC。|\r\n|基于子网划分|根据数据帧中的源IP地址和子网掩码来划分VLAN。

网络管理员预先配置IP地址和VLAN ID映射关系表,当交换机收到的是Untagged帧,就依据该表给数据帧添加指定VLAN的Tag。然后数据帧将在指定VLAN中传输。|- 当用户的物理位置发生改变,不需要重新配置VLAN。
- 可以减少网络通信量,可使广播域跨越多个交换机。|网络中的用户分布需要有规律,且多个用户在同一个网段。|适用于对安全需求不高、对移动性和简易管理需求较高的场景中。比如,一台PC配置多个IP地址分别访问不同网段的服务器,以及PC切换IP地址后要求VLAN自动切换等场景。|\r\n|基于协议划分|根据数据帧所属的协议(族)类型及封装格式来划分VLAN。

网络管理员预先配置以太网帧中的协议域和VLAN ID的映射关系表,如果收到的是Untagged帧,就依据该表给数据帧添加指定VLAN的Tag。然后数据帧将在指定VLAN中传输。|将网络中提供的服务类型与VLAN相绑定,方便管理和维护。|- 需要对网络中所有的协议类型和VLAN ID的映射关系表进行初始配置。
- 需要分析各种协议的格式并进行相应的转换,消耗交换机较多的资源,速度上稍具劣势。|适用于需要同时运行多协议的网络。|\r\n|基于策略(MAC地址、IP地址、接口)划分|根据配置的策略划分VLAN,能实现多种组合的划分方式,包括接口、MAC地址、IP地址等。

网络管理员预先配置策略,如果收到的是Untagged帧,且匹配配置的策略时,给数据帧添加指定VLAN的Tag。然后数据帧将在指定VLAN中传输。|- 安全性高,VLAN划分后,用户不能改变IP地址或MAC地址。
- 网络管理人员可根据自己的管理模式或需求选择划分方式。|针对每一条策略都需要手工配置。|适用于需求比较复杂的环境。|\r\n### 6.1.1. 静态vlan\r\n静态vlan是基于端口的vlan(Port Based VLAN)。顾名思义,就是明确指定各端口属于哪个vlan的设定方法。\r\n\t![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402091023895.png)\r\n由于需要一个个端口地指定,因此当网络中的计算机数目超过一定数字(比如数百台)后,设定操作就会变得烦杂无比。并且,客户机每次变更所连端口,都必须同时更改该端口所属VLAN的设定——这显然静态VLAN不适合那些需要频繁改变拓补结构的网络。\r\n### 6.1.2. 动态vlan\r\n动态VLAN则是根据每个端口所连的计算机,随时改变端口所属的VLAN。这就可以避免静态vlan的更改设定之类的操作。动态VLAN可以大致分为3类:\r\n- **基于MAC地址的VLAN(MAC Based VLAN)**。基于MAC地址的VLAN,就是通过查询并记录端口所连计算机上网卡的MAC地址来决定端口的所属。假定有一个MAC地址“A”被交换机设定为属于VLAN“10”,那么不论MAC地址为“A”的这台计算机连在交换机哪个端口,该端口都会被划分到VLAN10中去。计算机连在端口1时,端口1属于VLAN10;而计算机连在端口2时,则是端口2属于VLAN10。\r\n- **基于子网的VLAN(Subnet Based VLAN)**。基于子网的VLAN,则是通过所连计算机的IP地址,来决定端口所属VLAN的。不像基于MAC地址的VLAN,即使计算机因为交换了网卡或是其他原因导致MAC地址改变,只要它的IP地址不变,就仍可以加入原先设定的VLAN。\r\n- **基于用户的VLAN(User Based VLAN)**。基于用户的VLAN,则是根据交换机各端口所连的计算机上当前登录的用户,来决定该端口属于哪个VLAN。这里的用户识别信息,一般是计算机操作系统登录的用户,比如可以是Windows域中使用的用户名。这些用户名信息,属于OSI第四层以上的信息。\r\n其间的差异,主要在于根据OSI参照模型哪一层的信息决定端口所属的VLAN。\r\n### 6.1.3. Access接收帧\r\n1. 如果该帧是Untagged帧,则接收帧并打上接口的PVID(也就是该Access接口的default vlan或缺省VLAN);\r\n2. 如果该帧是Tagged帧,则当其VLAN-ID与接口PVID相同时,接收该帧,否则丢弃。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202401282306938.png)\r\n> 上图中,白色的大方框指的是交换机内部。\r\n### 6.1.4. Access发送帧\r\n在发送数据帧时,剥离其Tag,发出的帧为原始以太网帧,也就是**Untagged帧**。由于Access接口通常用于连接终端设备,而终端设备通常是只能够识别Untagged帧的,因此Access接口在发送数据帧时,始终是不会携带标记的(Access发送的数据报必定是无VLAN信息的数据包)。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202401282307990.png)\r\n当一个帧从Access口通过Trunk口传输到另一台交换机时,这个帧会携带VLAN信息,具体来说,它会包含802.1Q VLAN标记。在这个过程中,Access口连接的设备通常是终端设备,可能不发送带有VLAN标记的帧。然而,当帧通过Access口进入交换机后,交换机会为这个帧添加VLAN标记,然后将带有VLAN标记的帧发送到Trunk口。Trunk口的目的是在不同交换机之间传输多个VLAN的帧。为了实现这一点,Trunk口上传输的帧必须包含802.1Q VLAN标记。这样,接收端的交换机能够正确地识别帧属于哪个VLAN,并按照相应的VLAN进行处理。因此,从Access口发出的报文在通过Trunk口传输时,会带有802.1Q VLAN标记,以确保正确的VLAN信息被传递到网络中的其他设备。\r\n### 6.1.5. 总结\r\nAccess 端口可接受并转发的数据来源:\r\n1. 来自PC的无VLAN信息数据包;\r\n2. 从一个Access口入打上VLAN标记在交换机内转交给相同VLAN的access口去掉标记的无VLAN信息数据包;或者携带vlan标记转发给trunk口\r\n3. Access端口发送出去的数据包无VLAN信息,可被PC接受或Access口接收。\r\n\r\n当Access口收到报文后,打上PVID(默认VLAN ID)标记的主要作用有:\r\n1. 确保报文可以在该VLAN内部正确转发:打上PVID后,交换机就可以区分该报文属于哪个VLAN,从而进行正确的内部转发。\r\n2. 保证不同VLAN间的隔离:添加PVID标记后,不属于该VLAN的端口就不会收到或者转发这个报文,实现了VLAN间的隔离。\r\n\r\n Access口发出报文(目的是终端设备)去掉VLAN标记的主要原因有:\r\n1. Access口连接的是终端设备或者服务器等用户端设备,这些设备通常没有VLAN处理能力。如果发出的报文包含VLAN标记,这些设备无法识别,无法正常通信。\r\n2. 对用户端设备来说,它们只属于一个VLAN。没有必要在发出的报文中进行VLAN标记,用户端设备用不到这些信息。\r\n3. VLAN标记属于二层的标记信息。在用户网络这一端,更关心的是三层四层的服务和业务信息。所以VLAN信息对用户端意义不大。\r\n4. 去掉VLAN标记可以减少报文中的元数据,降低传输报文的开销,提升网络处理效率。\r\n5. 用户网络与服务网络的隔离常通过VLAN技术来实现。发往用户端的报文无需也不应包含VLAN信息,以免泄露网络拓扑信息。 \r\n6. 如果发往用户端报文包含VLAN,则用户可能利用这个信息进行网络攻击,产生安全隐患。\r\n## 6.2. trunk口\r\n到此为止,我们学习的都是使用单台交换机设置VLAN时的情况。那么,如果需要设置跨越多台交换机的VLAN时又如何呢?\r\n\r\n在规划企业级网络时,很有可能会遇到隶属于同一部门的用户分散在同一座建筑物中的不同楼层的情况,这时可能就需要考虑到如何跨越多台交换机设置VLAN的问题了。假设有如下图所示的网络,且需要将不同楼层的A、C和B、D设置为同一个VLAN。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402091040515.png)\r\n\r\n最简单的方法,自然是在交换机1和交换机2上各设一个红、蓝VLAN专用的接口并互联了。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402091040508.png)\r\n但是,这个办法从扩展性和管理效率来看都不好。例如,在现有网络基础上再新建VLAN时,为了让这个VLAN能够互通,就需要在交换机间连接新的网线。建筑物楼层间的纵向布线是比较麻烦的,一般不能由基层管理人员随意进行。并且,VLAN越多,楼层间(严格地说是交换机间)互联所需的端口也越来越多,交换机端口的利用效率低是对资源的一种浪费、也限制了网络的扩展。\r\n\r\n为了避免这种低效率的连接方式,人们想办法让交换机间互联的网线集中到一根上,这时使用的就是汇聚链接(Trunk Link),Trunk Link所连接的接口也叫做Trunk口。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402091042654.png)\r\n\r\n### 6.2.1. Trunk口接收帧\r\n- 若收到的数据帧是Untagged帧,则为数据帧打上接口PVID的标记,然后,如果PVID在接口允许通行(**Allow-pass**)的VLAN列表里,则接收该帧,若PVID不在允许通行的VLAN列表里,则丢弃。缺省时,Trunk接口的PVID为1,而且VLAN1缺省已经在允许通行的VLAN列表中。\r\n- 若收到的数据帧是Tagged帧,且其VLAN-ID在接口允许通行的VLAN列表里,则接收该帧。否则丢弃。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202401282309234.png)\r\n### 6.2.2. 发送帧\r\n- 若该帧的VLAN-ID与接口PVID相同,且该VLAN在允许通行的VLAN列表中,则去掉Tag,发送数据帧。\r\n- 若该帧的VLAN-ID与接口PVID不同,且该VLAN在允许通行的VLAN列表中,则保持原有Tag,发送该Tagged帧,而如果数据帧的VLAN-ID不在允许通行的VLAN列表中,则禁止从该接口发出。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202401282313377.png)\r\n# 7. VLAN间通信\r\n我们已经知道两台计算机即使连接在同一台交换机上,只要所属的VLAN不同就无法直接通信。接下来我们将要学习的就是如何在不同的VLAN间进行路由,使分属不同VLAN的主机能够互相通信。\r\n\r\n首先,先来复习一下为什么不同VLAN间不通过路由就无法通信。在LAN内的通信,必须在数据帧头中指定通信目标的MAC地址。而为了获取MAC地址,TCP/IP协议下使用的是ARP。ARP解析MAC地址的方法,则是通过广播。也就是说,如果广播报文无法到达,那么就无从解析MAC地址,亦即无法直接通信。\r\n\r\n计算机分属不同的VLAN,也就意味着分属不同的广播域,自然收不到彼此的广播报文。因此,属于不同VLAN的计算机之间无法直接互相通信。为了能够在VLAN间通信,需要利用OSI参照模型中更高一层——网络层的信息(IP地址)来进行路由。\r\n\r\n路由功能,一般主要由路由器提供。但在今天的局域网里,我们也经常利用带有路由功能的交换机——三层交换机(Layer 3 Switch)来实现。\r\n## 7.1. vlanif口\r\nLANIF口(VLAN Interface,VLAN接口)是在一些网络设备中用于与虚拟局域网(VLAN)进行交互的逻辑接口。它的作用有以下几个方面:\r\n- 实现VLAN与网络设备的连接:VLANIF口可用于将VLAN与物理设备(如交换机)连接起来。它相当于将VLAN虚拟出来,使得设备能够理解和处理来自VLAN的信息。\r\n- 实现VLAN间的互通:当网络中存在多个VLAN时,需要通过某些方式实现它们之间的互通。VLANIF口可以作为VLAN之间的交换口,允许不同VLAN中的设备之间进行通信。\r\n- 提供逻辑接口与上层网络交互:VLANIF口具备与上层网络进行通信的能力,如与路由器相连,从而实现不同VLAN之间的跨网段通信。\r\n- 配置IP地址和其他网络参数:VLANIF口可以配置IP地址,使得其具备在网络中进行通信的能力。此外,还可以配置其他网络参数,如子网掩码、默认网关等。\r\n总的来说,VLANIF口在网络设备中充当了连接不同VLAN和上层网络的桥梁,实现了VLAN的隔离和互通,同时也提供了管理和配置VLAN的功能。\r\n在一些网络设备中,一个VLAN可以配置多个VLANIF口(VLAN Interface)。这意味着一个VLAN可以与多个逻辑接口进行关联,从而实现更灵活的网络配置和管理。有多个VLANIF口的情况可以发生在以下场景:\r\n- **分段网络**:当一个VLAN需要被划分成多个子网时,可以为每个子网创建一个独立的VLANIF口。这样可以为每个子网配置不同的IP地址、子网掩码、默认网关等网络参数。\r\n- **逻辑隔离**:有时候在同一个VLAN中需要实现逻辑上的隔离,例如将同一VLAN内的设备划分为不同的逻辑组。为每个逻辑组创建单独的VLANIF口可以提供更细粒度的管理和控制。\r\n- **多网关设置**:某些情况下,一个VLAN可能需要使用多个默认网关。通过为该VLAN配置多个VLANIF口,可以为每个接口指定不同的默认网关,实现使用不同网关的多路径选择。\r\n需要注意的是,并非所有的网络设备都支持多个VLANIF口的配置。具体支持的数量和配置方法可能会因设备类型和厂商而异。在配置多个VLANIF口之前,建议参考设备的文档或咨询设备厂商,以确保设备支持所需的功能和配置。\r\n## 7.2. 使用路由器进行vlan间路由\r\n在使用路由器进行VLAN间路由时,与构建横跨多台交换机的VLAN时的情况类似,我们还是会遇到“该如何连接路由器与交换机”这个问题。路由器和交换机的接线方式,大致有以下两种:\r\n- 将路由器与交换机上的每个VLAN分别连接\r\n- 不论VLAN有多少个,路由器与交换机都只用一条网线连接\r\n### 7.2.1. access口连接\r\n最容易想到的,当然还是“把路由器和交换机以VLAN为单位分别用网线连接”了。将交换机上用于和路由器互联的每个端口设为access口,然后分别用网线与路由器上的独立端口互联。如下图所示,交换机上有2个VLAN,那么就需要在交换机上预留2个端口用于与路由器互联;路由器上同样需要有2个端口;两者之间用2条网线分别连接。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161451453.png)\r\n如果采用这个办法,大家应该不难想象它的扩展性很成问题。每增加一个新的VLAN,都需要消耗路由器的端口和交换机上的访问链接,而且还需要重新布设一条网线。而路由器通常不会带有太多LAN接口的。新建VLAN时,为了对应增加的VLAN所需的端口,就必须将路由器升级成带有多个LAN接口的高端产品,这部分成本、还有重新布线所带来的开销,都使得这种接线法成为一种不受欢迎的办法。\r\n### 7.2.2. Trunk口连接\r\n那么,第二种办法“不论VLAN数目多少,都只用一条网线连接路由器与交换机”呢?当使用一条网线连接路由器与交换机、进行VLAN间路由时,需要用到Trunk口。\r\n\r\n具体实现过程为:首先将用于连接路由器的交换机端口设为Trunk口,而路由器上的端口也必须支持汇聚链路。双方用于汇聚链路的协议自然也必须相同。接着在路由器上定义对应各个VLAN的“子接口(Sub Interface)”。尽管实际与交换机连接的物理端口只有一个,但在理论上我们可以把它分割为多个虚拟端口。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161454307.png)\r\nVLAN将交换机从逻辑上分割成了多台,因而用于VLAN间路由的路由器,也必须拥有分别对应各个VLAN的虚拟接口。采用这种方法的话,即使之后在交换机上新建VLAN,仍只需要一条网线连接交换机和路由器。用户只需要在路由器上新设一个对应新VLAN的子接口就可以了。与前面的方法相比,这种方法扩展性要强得多,也不用担心需要升级LAN接口数不足的路由器或是重新布线。\r\n\r\n#### 7.2.2.1. 通信原理\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161457179.png)\r\n红色VLAN(VLAN ID=1)的网络地址为192.168.1.0/24,蓝色VLAN(VLAN ID=2)的网络地址为192.168.2.0/24。各计算机的MAC地址分别为A/B/C/D,路由器汇聚链接端口的MAC地址为R。交换机通过对各端口所连计算机MAC地址的学习,生成如下的MAC地址列表。\r\n\r\n|端口|MAC地址|VLAN|\r\n|---|---|---|\r\n|1|A|1|\r\n|2|B|1|\r\n|3|C|2|\r\n|4|D|2|\r\n|5|-|-|\r\n|6|R|汇聚|\r\n\r\n首先考虑计算机A与同一VLAN内的计算机B之间通信时的情形。\r\n1. 计算机A发出ARP请求信息,请求解析B的MAC地址。\r\n2. 交换机收到数据帧后,检索MAC地址列表中与收信端口同属一个VLAN的表项。\r\n3. 结果发现,计算机B连接在端口2上,于是交换机将数据帧转发给端口2,最终计算机B收到该帧。\r\n收发信双方同属一个VLAN之内的通信,一切处理均在交换机内完成。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161459333.png)\r\n接下来是这一讲的核心内容,不同VLAN间的通信。让我们来考虑一下计算机A与计算机C之间通信时的情况。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161500760.png)\r\n1. 计算机A从通信目标的IP地址(192.168.2.1)得出C与本机不属于同一个网段。因此会向设定的默认网关(Default Gateway,GW)转发数据帧。在发送数据帧之前,需要先用ARP获取路由器的MAC地址。\r\n2. 得到路由器的MAC地址R后,接下来就是按图中所示的步骤发送往C去的数据帧。①的数据帧中,目标MAC地址是路由器的地址R、但内含的目标IP地址仍是最终要通信的对象C的地址。这一部分的内容,涉及到局域网内经过路由器转发时的通信步骤。\r\n3. 交换机在端口1上收到①的数据帧后,检索MAC地址列表中与端口1同属一个VLAN的表项。由于汇聚链路会被看作属于所有的VLAN,因此这时交换机的端口6也属于被参照对象。这样交换机就知道往MAC地址R发送数据帧,需要经过端口6转发。从端口6发送数据帧时,由于它是汇聚链接,因此会被附加上VLAN识别信息。由于原先是来自红色VLAN的数据帧,因此如图中②所示,会被加上红色VLAN的识别信息后进入汇聚链路。\r\n4. 路由器收到②的数据帧后,确认其VLAN识别信息,由于它是属于红色VLAN的数据帧,因此交由负责红色VLAN的子接口接收。接着,根据路由器内部的路由表,判断该向哪里中继。由于目标网络192.168.2.0/24是蓝色VLAN,且该网络通过子接口与路由器直连,因此只要从负责蓝色VLAN的子接口转发就可以了。这时,数据帧的目标MAC地址被改写成计算机C的目标地址;并且由于需要经过汇聚链路转发,因此被附加了属于蓝色VLAN的识别信息。这就是图中③的数据帧。\r\n5. 交换机收到③的数据帧后,根据VLAN标识信息从MAC地址列表中检索属于蓝色VLAN的表项。由于通信目标——计算机C连接在端口3上、且端口3为普通的access口,因此交换机会将数据帧除去VLAN识别信息后(数据帧④)转发给端口3,最终计算机C才能成功地收到这个数据帧。\r\n\r\n进行VLAN间通信时,即使通信双方都连接在同一台交换机上,也必须经过:“发送方——交换机——路由器——交换机——接收方”这样一个流程。\r\n\r\n## 7.3. 使用三层交换机进行vlan间路由\r\n现在,我们知道只要能提供VLAN间路由,就能够使分属不同VLAN的计算机互相通信。但是,如果使用路由器进行VLAN间路由的话,随着VLAN之间流量的不断增加,很可能导致路由器成为整个网络的瓶颈。交换机使用被称为ASIC(Application Specified Integrated Circuit)的专用硬件芯片处理数据帧的交换操作,在很多机型上都能实现以缆线速度(Wired Speed)交换。而路由器,则基本上是基于软件处理的。即使以缆线速度接收到数据包,也无法在不限速的条件下转发出去,因此会成为速度瓶颈。就VLAN间路由而言,流量会集中到路由器和交换机互联的汇聚链路部分,这一部分特别容易成为速度瓶颈。并且从硬件上看,由于需要分别设置路由器和交换机,在一些空间狭小的环境里可能连设置的场所都成问题。\r\n\r\n为了解决上述问题,三层交换机应运而生。三层交换机,本质上就是“带有路由功能的(二层)交换机”。路由属于OSI参照模型中第三层网络层的功能,因此带有第三层路由功能的交换机才被称为“三层交换机”。关于三层交换机的内部结构,可以参照下面的简图。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161513965.png)\r\n在一台本体内,分别设置了交换机模块和路由器模块;而内置的路由模块与交换模块相同,使用ASIC硬件处理路由。因此,与传统的路由器相比,可以实现高速路由。并且,路由与交换模块是汇聚链接的,由于是内部连接,可以确保相当大的带宽。\r\n\r\n在三层交换机内部数据传播基本上和使用汇聚链路连接路由器与交换机时的情形相同。假设有如下图所示的4台计算机与三层交换机互联。当使用路由器连接时,一般需要在LAN接口上设置对应各VLAN的子接口;而三层交换机则是在内部生成“VLAN接口(VLAN Interface)”。VLAN接口,是用于各VLAN收发数据的接口。\r\n> 在Cisco的Catalyst系列交换机上,VLAN Interface被称为SVI——Switched Virtual Interface\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161517074.png)\r\n为了与使用路由器进行VLAN间路由对比,让我们同样来考虑一下计算机A与计算机B之间通信时的情况。首先是目标地址为B的数据帧被发到交换机;通过检索同一VLAN的MAC地址列表发现计算机B连在交换机的端口2上;因此将数据帧转发给端口2。\r\n\r\n接下来设想一下计算机A与计算机C间通信时的情形。针对目标IP地址,计算机A可以判断出通信对象不属于同一个网络,因此向默认网关发送数据(Frame 1)。交换机通过检索MAC地址列表后,经由内部汇聚链接,将数据帧转发给路由模块。在通过内部汇聚链路时,数据帧被附加了属于红色VLAN的VLAN识别信息(Frame 2)。路由模块在收到数据帧时,先由数据帧附加的VLAN识别信息分辨出它属于红色VLAN,据此判断由红色VLAN接口负责接收并进行路由处理。因为目标网络192.168.2.0/24是直连路由器的网络、且对应蓝色VLAN;因此,接下来就会从蓝色VLAN接口经由内部汇聚链路转发回交换模块。在通过汇聚链路时,这次数据帧被附加上属于蓝色VLAN的识别信息(Frame 3)。交换机收到这个帧后,检索蓝色VLAN的MAC地址列表,确认需要将它转发给端口3。由于端口3是通常的访问链接,因此转发前会先将VLAN识别信息除去(Frame 4)。最终,计算机C成功地收到交换机转发来的数据帧。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161520487.png)\r\n整体的流程,与使用外部路由器时的情况十分相似——都需要经过“发送方→交换模块→路由模块→交换模块→接收方”这样的流程。\r\n\r\n### 7.3.1. 加速vlan通信的手段\r\n根据到此为止的学习,我们已经知道VLAN间路由,必须经过外部的路由器或是三层交换机的内置路由模块。但是,有时并不是所有的数据都需要经过路由器(或路由模块)。例如,使用FTP(File Transfer Protocol)传输容量为数MB以上的较大的文件时,由于MTU的限制,IP协议会将数据分割成小块后传输、并在接收方重新组合。这些被分割的数据,“发送的目标”是完全相同的。发送目标相同,也就意味着同样的目标IP地址、目标端口号(注:特别强调一下,这里指的是TCP/UDP端口)。自然,源IP地址、源端口号也应该相同。这样一连串的数据流被称为“流(Flow)”。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161526042.png)\r\n只要将流最初的数据正确地路由以后,后继的数据理应也会被同样地路由。据此,后继的数据不再需要路由器进行路由处理;通过省略反复进行的路由操作,可以进一步提高VLAN间路由的速度。\r\n\r\n接下来,让我们具体考虑一下该如何使用三层交换机进行高速VLAN间路由。首先,整个流的第一块数据,照常由交换机转发→路由器路由→再次由交换机转发到目标所连端口。这时,将第一块数据路由的结果记录到缓存里保存下来。需要记录的信息有:\r\n- 目标IP地址\r\n- 源IP地址\r\n- 目标TCP/UDP端口号\r\n- 源TCP/UDP端口号\r\n- 接收端口号(交换机)\r\n- 转发端口号(交换机)\r\n- 转发目标MAC地址\r\n同一个流的第二块以后的数据到达交换机后,直接通过查询先前保存在缓存中的信息查出“转发端口号”后就可以转发给目标所连端口了。这样一来,就不需要再一次次经由内部路由模块中继,而仅凭交换机内部的缓存信息就足以判断应该转发的端口。这时,交换机会对数据帧进行由路由器中继时相似的处理,例如改写MAC地址、IP包头中的TTL和Check Sum校验码信息等。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202402161528555.png)\r\n通过在交换机上缓存路由结果,实现了以缆线速度(Wired Speed)接收发送方传输来数据的数据、并且能够全速路由、转发给接收方。\r\n\r\n需要注意的是,类似的加速VLAN间路由的手法多由各厂商独有的技术所实现,并且该功能的称谓也因厂商而异。例如,在Cisco的Catalyst系列交换机上,这种功能被称为“多层交换(Multi Layer Switching)”。另外,除了三层交换机的内部路由模块,外部路由器中的某些机型也支持类似的高速VLAN间路由机制。\r\n## 7.4. 路由器存在的意义\r\n三层交换机的价格,在问世之初非常昂贵,但是现在它们的价格已经下降了许多。目前国外一些廉价机型的售价,折合成人民币后仅为一万多元,而且还在继续下降中。\r\n\r\n既然三层交换机能够提供比传统型路由器更为高速的路由处理,那么网络中还有使用路由器的必要吗?\r\n\r\n答案是:“是”。\r\n\r\n使用路由器的必要性,主要表现在以下几个方面:\r\n- **用于与WAN连接**,三层交换机终究是“交换机”。也就是说,绝大多数机型只配有LAN(以太网)接口。在少数高端交换机上也有用于连接WAN的串行接口或是ATM接口,但在大多数情况下,连接WAN还是需要用到路由器。\r\n- **保证[网络安全](https://cloud.tencent.com/product/ns?from_column=20065&from=20065)**,在三层交换机上,通过数据包过滤也能确保一定程度的网络安全。但是使用路由器所提供的各种网络安全功能,用户可以构建更为安全可靠的网络。路由器提供的网络安全功能中,除了最基本的数据包过滤功能外,还能基于IPSec构建×××(Virtual Private Network)、利用RADIUS进行用户认证等等。\r\n- **支持除TCP/IP以外的网络架构**,尽管TCP/IP已经成为当前网络协议架构的主流,但还有不少网络利用Novell Netware下的IPX/SPX或Macintosh下的Appletalk等网络协议。三层交换机中,除了部分高端机型外基本上还只支持TCP/IP。因此,在需要使用除TCP/IP之外其他网络协议的环境下,路由器还是必不可少的。\r\n\r\n> 在少数高端交换机上,也能支持上述路由器的功能。例如Cisco的Catalyst6500系列,就可以选择与WAN连接的接口模块;还有可选的基于IPSec实现×××的模块;并且也能支持TCP/IP以外的其他网络协议。\r\n# 8. QinQ报文封装格式\r\n因为IEEE 802.1Q中定义的VLAN Tag域只有12个比特,仅能表示4096个VLAN,无法满足城域以太网中标识大量用户的需求,于是产生了QinQ技术,拓展VLAN的数量空间。QinQ在原有的802.1Q报文的基础上增加一层802.1Q标签,使得VLAN数量增加到4094×4094。\r\n在公网的传输过程中,设备只根据外层VLAN Tag转发报文,并根据报文的外层VLAN Tag进行MAC地址学习,而用户的内层VLAN Tag将被当作报文的数据部分进行传输。QinQ的内外层标签可以代表不同的信息,如内层标签代表用户,外层标签代表业务。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/QinQ.png)\r\n\r\n# 9. Ref\r\n[3. 网络基本功(三):细说VLAN与Trunk - 《网络基本功系列》 - 书栈网 · BookStack](https://www.bookstack.cn/read/network-basic/3.md)\r\n[VLAN及Trunk,重要!看瑞哥如何讲的明明白白!-腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1741191)\r\n[图文并茂VLAN详解,让你看一遍就理解VLAN-腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1412795)"},{"id":"交换机原理","title":"交换机原理","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"交换机原理","description":"1. SAT(MAC地址表、FDB、CAM) 交换机的Source Address Table(源地址表),也称为MAC地址表、FDB(forwarding database)或CAM(Content Addressable Memory),是一种用于存储和管理设备MAC地址和端口之间映射关系的表格...","relativePath":"Tech/Network/数据链路层/交换机原理.md","rawContent":"# 1. SAT(MAC地址表、FDB、CAM)\r\n交换机的Source Address Table(源地址表),也称为MAC地址表、FDB(forwarding database)或CAM(Content Addressable Memory),是一种用于存储和管理设备MAC地址和端口之间映射关系的表格。当交换机接收到一个帧(以太网数据包)时,它会查看帧中的源MAC地址,并在源地址表中查找与该MAC地址相关的端口信息。如果表中存在该地址的映射,则交换机将确定目标MAC地址对应端口,并将帧转发到目标端口。如果表中不存在该地址的映射,交换机将广播(flooding)这个帧到所有其他端口,以便学习新的MAC地址与端口的映射关系,并在以后的转发中使用这些信息进行优化。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/mac%E5%9C%B0%E5%9D%80%E8%A1%A8.png)\r\n"},{"id":"概念解释","title":"概念解释","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"概念解释","description":"ISP: Internet Service Provider 互联网服务提供商 IXP: Internet eXchange Point 互联网交换点 通信方式: - 客户-服务器方式(C/S) - 对等方式(P2P) 1. 电路交换 电路交换必须经过“建立连接(占用 通信资源)一通话(一直占用通信...","relativePath":"Tech/Network/概述/概念解释.md","rawContent":"ISP: Internet Service Provider 互联网服务提供商\r\nIXP: Internet eXchange Point 互联网交换点\r\n\r\n通信方式:\r\n- 客户-服务器方式(C/S)\r\n- 对等方式(P2P)\r\n# 1. 电路交换\r\n电路交换必须经过“建立连接(占用 通信资源)一通话(一直占用通信资源)一释放连接(归还迎信资源)”三个步骤。在通话的时间段里面,通信的双方始终占用端到端的通信资源。\r\n\r\n发展历史:\r\n人工交换机 -> 步进制交换机 -> 纵横制自动电话交换机 -> 程控交换机\r\n\r\n多路复用技术:\r\n空分复用、时分复用、波分(频分)复用、码分复用\r\n\r\n传输介质:\r\n电缆、光缆 \r\n\r\n# 2. 分组交换机\r\n不需要建立连接,只有分组报文在链路上传播时才占用带宽。到达路由器时暂存-查表-转发。\r\n\r\n> 若要连续传送大量的数据,且其传送时间远大于连接建立时间,则电路交换的传输速率较快(不需要传输分组交换需要的额外控制信息)。分组交换不需要预先分配传输带宽,在传送突发数据时可提高整个网络的信道的利用率。"},{"id":"IPSec","title":"IPSec","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":22,"slug":"ipsec","description":"IPSec (Internet Protocol Security) 协议主要工作在OSI模型的第3层网络层,为上层传输层协议(如TCP、UDP等)提供安全支持。IPSec不是一个单独网络协议,而是一系列为IP网络提供安全性的协议和服务的集合,提供了认证、加密、数据完整性等安全服务。 1. 主要协议...","relativePath":"Tech/Network/网络层/IPSec/IPSec.md","rawContent":"IPSec (Internet Protocol Security) 协议主要工作在OSI模型的第3层网络层,为上层传输层协议(如TCP、UDP等)提供安全支持。IPSec不是一个单独网络协议,而是一系列为IP网络提供安全性的协议和服务的集合,提供了认证、加密、数据完整性等安全服务。\r\n# 1. 主要协议\r\n具体来说,IPSec 主要包括以下三种协议:\r\n- IKE协议(Internet Key Exchange)是主要的密钥管理协议,用于建立IPsec安全通道。AH和ESP是用于保护IP数据报的安全协议。IKE是IPsec的“大脑”,为AH和ESP“拿钥匙”开门。三者关系密不可分,共同实现了IPsec的安全机制。\r\n- AH协议(Authentication Header): 提供连接的双方认证和数据完整性保护。\r\n- ESP协议(Encapsulating Security Payload): 提供数据机密性保护通过加密和可选的认证服务。\r\n# 2. 功能\r\n- 数据来源验证:接受方验证发送方身份是否合法\r\n- 数据加密:发送方对发送的数据加密,以密文的形式在internet上传送,接收方对接收的加密数据进行解密或直接转发\r\n- 数据完整性:接收方对数据完整性进行校验,以确定数据是否被篡改\r\n- 抗重放:接收方拒绝旧的或重复的数据包,防止恶意用户通过重复发送截获的数据包进行攻击\r\n## 2.1. 数据来源验证\r\nIPsec利用数字签名机制来实现数据来源的验证,主要步骤是:\r\n1. 签名者使用其私钥对数据报文生成数字签名。\r\n2. 将原始数据报文和签名一起发送给接收方。\r\n3. 接收方使用签名者的公钥验证签名是否有效。\r\n4. 如果验证成功,说明该报文来自持有相匹配私钥的签名者,数据来源得到验证。\r\n5. 如果验证失败,说明报文不是来自声称的签名者,或者报文在传输中被篡改过。\r\n数字签名的安全性基于公钥加密算法的密码学特性,只有对应的私钥持有者才能生成正确的签名。另外,数字签名还提供非抵赖性,签名者无法抵赖自己生成的合法签名。通过这种机制,IPsec可以验证数据报文的真实来源,防止源地址伪造或中间人攻击。这是IPsec实现数据真实性和完整性的关键手段之一。\r\n## 2.2. 加密\r\nIPsec在加密数据本身时,使用的是对称加密算法,比如AES、3DES等,速度快。\r\n具体来说:\r\n1. IPsec各方通过密钥交换协议(如IKE)协商生成会话密钥。\r\n2. 会话密钥采用对称加密算法,双方都持有相同的会话密钥。\r\n3. 使用会话密钥对数据报文进行对称加密,获得加密后的数据。\r\n4. 在传输过程中,该会话密钥不会再传输,保证密钥的安全。\r\n5. 接收方用同样的会话密钥进行对称解密,获取原始报文数据。\r\n而IPsec中使用的数字签名,才是采用非对称加密算法,比如RSA等。\r\n## 2.3. 完整性校验\r\n在中间人无法获取加密密钥的场景下,IPsec协议中的完整性校验实际上就是一个签名与验签的过程\r\n具体来说:\r\n1. 发送方会使用预共享密钥或者私钥加密生成报文的签名。\r\n2. 接收方利用相同的密钥或发送方的公钥来验证签名的正确性。\r\n3. 如果签名验证失败,说明报文遭到篡改,接收方应丢弃此报文。\r\n4. 正确的签名验证表示报文完整、真实,可以信任报文的内容。\r\n所以在这种情况下,完整性校验就是对报文内容进行数字签名,通过签名的正确性来验证报文的完整性和真实性。\r\n## 2.4. 为什么要同时进行加密和完整性校验(签名)\r\n即使在中间人无法获取加密密钥的情况下篡改报文,进行完整性校验也是非常必要的,主要出于以下几点考虑:\r\n1. 可以检测到篡改行为的存在。如果不做完整性校验,接收方将无法知道报文已经被篡改过。\r\n2. 可以及时放弃解密。完整性校验失败可以直接丢弃报文,不需要花费资源去尝试解密。\r\n3. 防止错误传播。即使解密失败,若不做完整性校验,接收方还可能会对错误数据进行后续处理,导致错误扩散。\r\n4. 有助于实时检测入侵行为。完整性校验失败可以及时告警,提示有中间人入侵行为。\r\n5. 保护解密模块的安全。解密失败的数据可能会触发解密模块的安全漏洞,完整性校验可以避免这种情况发生。\r\n所以即使报文无法被成功解密,进行完整性校验也是非常必要的,可以提高安全性、资源利用效率并及时检测入侵。\r\n## 2.5. 抗重放\r\n完整性校验会使用序号或时间戳等机制,这可以防止截获的数据包被复用重放。\r\n# 3. IPsec框架\r\n# 4. IKE\r\n在IPsec中,IKE负责协商和生成AH和ESP使用的密钥材料。AH和ESP依赖IKE生成的密钥来实现其数据认证,加密等安全功能。IKE会先与对端建立安全通道,协商安全参数,然后生成发送给AH和ESP的密钥。AH和ESP使用IKE提供的密钥来实现对IP数据报的认证和加密,从而保证通信安全。IKE还可以在密钥过期后负责重建安全通道和协商新密钥。IKE,AH和ESP一般配合使用,共同提供IPsec的安全服务。\r\n## 4.1. 工作原理\r\n1. 建立通信通道:IKE首先在通信双方之间建立一个不安全的通信通道,用于后续的协商通信。这个通道通常使用UDP端口500。\r\n2. 协商加密算法:双方通过前面建立的通道协商使用哪种加密算法来保护后续通信的安全,比如DES、AES等。\r\n3. 协商认证机制:双方协商使用哪种方式来互相认证通信方身份,比如预共享密钥、数字证书等。\r\n4. 生成并交换密钥:根据协商的加密算法,双方各自随机产生密钥,并通过不安全通道进行交换。\r\n5. 建立安全通道:使用交换的密钥建立真正的安全通道(IPSec VPN隧道),用于后续的数据传输。\r\n6. 定期重新协商:IKE会定期改变并重新协商密钥,以保证长期通信的安全性。\r\n7. 关闭通道:当通信结束时,IKE负责删除协商产生的密钥材料,关闭建立的安全通道。\r\n### 4.1.1. 如何实现双方的身份认证\r\n#### 4.1.1.1. 预共享秘钥\r\n预共享密钥身份验证要求在协商密钥前预先把参与通讯的各方安全网关对应的密钥先保存在安全网关上,在IP地址不固定的情况下只能采取所有的安全网关都用相同的密钥,这显然只能适合小型且对安全性要求不高的网络。共享密钥优点是软件实现起来比较容易,配置简单,且设备投资比较小。\r\n\r\n实现方法如下:\r\n1. 预先配置密钥:在通信双方(例如 VPN 网关)上预先配置相同的共享密钥,并确保密钥的安全存储。\r\n2. IKE协商使用PSK:在IKE协商过程中,双方选择使用预共享密钥作为认证方式。\r\n3. 使用密钥散列值认证:双方各自使用预共享密钥和协商数据通过散列函数生成一个密钥标识(Key ID)。Key ID = Hash(PSK + 协商数据)\r\n4. 交换密钥标识:通信双方将生成的密钥标识发送给对方。\r\n5. 验证标识:收到标识后,使用本地的预共享密钥验证能否生成出一致的密钥标识。\r\n6. 重复步骤验证:重复上述步骤,双方都能通过标识成功验证对方身份。\r\n7. 建立安全通道:验证成功后,利用协商生成的密钥建立安全的通信通道。\r\n\r\n#### 4.1.1.2. 数字证书\r\n1. 双方配置数字证书:通信双方的设备上都需要预先配置数字证书,通常是由受信任的CA机构签发(ca签名,证明持有者可信)。证书中绑定了设备的身份信息和公钥。\r\n2. 开始IKE协商:在IKE的第一个阶段,双方建立通信通道,商定协议版本、加密算法等。\r\n3. 交换证书:在第二阶段,双方交换数字证书,通常在第2个IKE消息中完成。\r\n4. 验证证书:接收方收到对方的证书后,会进行如下验证:\r\n\t1. 检查证书是否过期、是否吊销\r\n\t2. 验证证书签名,通过CA的公钥解密签名,验证签名正确性\r\n\t3. 检查证书主体内容,确认对端身份信息\r\n5. 提取公钥: 验证通过后,从证书中提取对方的公钥。\r\n6. 签名验证: 用提取的公钥验证对方在协商中消息签名的正确性。\r\n7. 建立安全通道: 如果上述证书验证和签名验证都成功,则表明对方身份合法,可以继续后续协商,建立安全的IKE通道。\r\n\r\n# 5. AH\r\nAH在IKE完成IKE认证和秘钥协商的基础上,提供以下功能:\r\n- IKE的认证只能证明IKE端点本身的身份合法,不能保证IP数据包的完整性。\r\n- AH可以利用序列号防止重放攻击\r\n- AH为整个IP数据包提供认证服务,而不仅仅是IKE通信负载数据。\r\n## 5.1. 协议格式\r\nAH是一种基于IP的传输层协议,协议号为51,协议的具体格式如下:\r\n- Next Header: 标识AH报文头后面的负载类型。传输模式下,是被保护的上层协议(TCP或UDP)或ESP协议的编号;隧道模式下,是IP协议或ESP协议的编号。注意:当AH与ESP协议同时使用时,AH报文头的下一头部为ESP报文头。\r\n- Payload Length: AH头部自身的长度。\r\n- Reserved: 保留未用,设置为0。\r\n- Security Parameters Index (SPI): 安全参数索引,标识AH协议使用的安全联盟。\r\n- Sequence Number: 是一个从1开始的单项递增的计数器,唯一地标识每一个数据包,用于防止重放攻击。\r\n- Authentication Data: 认证数据,存放认证的散列值,即完整性校验值 ICV(Integrity Check Value),用于接收方进行完整性校验。可选择的认证算法有MD5、SHA1、SHA2、SM3。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202310202153359.png)\r\n## 5.2. 协议抓包\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202310202208557.png)\r\n## 5.3. SPI\r\n### 5.3.1. SA\r\nIPsec中的安全关联(Security Association, SA)是通过IKE(Internet Key Exchange)协议进行协商和生成的。 \r\n1. 协商阶段: 双方协商生成SA的参数,比如加密算法,Hash算法,密钥生存时间等。\r\n2. 对称密钥协商: 双方协商生成用于数据传输的对称加密密钥。\r\n3. 非对称签名: 用数字证书对对称密钥进行签名,确保密钥的完整性。\r\n4. SA生成: 将协商结果填入数据结构,生成SA的参数定义,包括SPI,密钥,协议等信息。\r\n5. SA写入SAD: 将新生成的SA写入双方的安全关联数据库(SAD)。\r\n6. SPI绑定: 为新SA分配一个唯一的SPI值,与SA进行绑定。\r\n7. 提交使用: 将新SA提交给IPsec,进行数据传输安全处理。\r\n8. SA定期更新: 更新密钥,重新协商等,维持长期安全。\r\n### 5.3.2. SAD\r\nSAD(Security Association Database) 指IPsec设备上用于维护安全关联(SA)信息的数据库。其主要功能包括:\r\n1. 存储设备上配置或协商生成的所有活动和备用SA信息。\r\n2. 每个SA通过一个唯一的SPI进行索引。\r\n3. IPsec在处理报文时,通过查找SAD获取匹配SPI的SA参数。\r\n4. SAD可由本地静态配置构成,也可以结合IKE动态协商生成。\r\n5. 当SA失效时,从SAD中删除该SA信息。\r\n6. SAD需要高速索引与查找能力,可用硬件优化。\r\n7. SAD中的SA和密钥需要安全存储和管理。\r\nSAD是维护IPsec SA信息的关键数据库,它为IPsec核心提供需要的安全参数以进行加密、认证和访问控制处理。SAD的效率直接影响IPsec性能。\r\n### 5.3.3. SPI\r\nSPI(Security Parameters Index)是一个重要的字段,它的主要作用是:\r\n1. 标识一个唯一的安全关联(Security Association, SA)。每个IPsec安全关联都有一个唯一的SPI值与其对应。\r\n2. 安全关联中决定了使用的加密算法、认证算法、密钥等参数。\r\n3. 接收方根据SPI找到正确的安全关联,从而取出正确的密钥进行解密和认证。\r\n4. SPI和安全关联的映射关系存储在设备的安全关联数据库(SAD)中。\r\n5. SPI可以帮助接收端并行处理不同的安全通信通道。\r\n6. SPI通常是一个32比特的值,可以使得不同的通信流具有唯一标识。\r\n所以SPI是一个索引值,它与特定的安全关联绑定,通过SPI可以找出正确的参数来进行ipsec安全处理。它对于接收端识别和处理不同的安全流是非常关键的。\r\n## 5.4. Authentication Data\r\n具体生成过程如下:\r\n1. 将整个原始IP报文作为输入,串联各个字段。\r\n2. 报文中会插入一个32位的SPI字段,标识所使用的安全关联。\r\n3. 再插入一个序列号字段,用来进行抗重放保护。\r\n4. 最后输入一个共享的HMAC密钥(在IKE阶段生成)\r\n5. 将以上内容作为输入,经过HMAC哈希算法计算,生成定长的HASH值。\r\n6. 这个HASH值即为Authentication Data,存储在AH头部中。\r\n接收方利用同样的HMAC密钥、SPI、序列号等参数进行相同的HASH计算。如果计算结果与AH头部中的Authentication Data相匹配,则证明报文在传输过程中没有被修改,验证成功。这种基于HMAC的认证数据生成方式,可为AH提供完整性保护和数据源鉴别功能。\r\n> 基与HMAC的方式实现了报文的完整性校验,而基与私钥的签名则提供了来源验证的能力。\r\n## 5.5. Sequence Number\r\nAH头中还包含一个序列号,用来防重放攻击。 \r\n1. 当建立IPsec安全关联(SA)时,发送方和接收方协商一个sequence number计数器的初始值,例如0。\r\n2. 发送方对每个报文包按顺序赋予一个唯一的sequence number。\r\n3. 此number被加入AH或ESP头部。\r\n4. 接收方接收报文时,会检查sequence number。\r\n5. 如果number大于预期值,则表明是一个新的报文,接受并更新number。\r\n6. 如果number小于或等于预期值,说明是一个重复老报文,丢弃该重放报文。\r\n7. 接收方维护一个滑动窗口,以容忍网络正常的少量乱序。\r\n8. 发送方和接收方都需要持续维护并同步sequence number。\r\n9. 当number达到最大值后,会重新循环从0开始。\r\n通过这种机制,可以有效防止IPsec重放攻击。它依赖发送接收双方对sequence number的维护与校验。\r\n# 6. ESP\r\nAH算法并不能保证报文的私密性,通常需要和ESP一起使用,前者保证完整性,后者提供加密。\r\n# 7. 隧道模式\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202310192243313.png)\r\n\r\n\r\n# 8. 总结\r\n- 如果只需要认证和完整性保护,可以单独使用 AH。\r\n- 如果只需要加密,可以单独使用 ESP。\r\n- 如果需要认证、完整性保护和加密,需要同时使用AH和ESP。AH和ESP都定义在IPSec标准中,通常一起使用来提供全面的安全保护。\r\n\r\n\r\n# 9. Ref\r\n[ipsec(AH和ESP) - kk Blog —— 通用基础 (abcdxyzk.github.io)](https://abcdxyzk.github.io/blog/2021/06/15/net-ipsec-ah-esp/)\r\n[IPSec VPN基本原理 | 曹世宏的博客 (cshihong.github.io)](https://cshihong.github.io/2019/04/03/IPSec-VPN%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/)\r\n\r\n封装顺序:使用AH和ESP时,需要先封装AH再封装ESP,因为AH需要计算整个包(包含ESP加密数据)的认证信息来保证完整性。"},{"id":"仿真","title":"仿真","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"仿真","description":"","relativePath":"Tech/Network/网络层/IPSec/仿真.md","rawContent":""},{"id":"Ipv4地址","title":"Ipv4地址","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"ipv4地址","description":"1. 特殊ip地址汇总 - 0.0.0.0/8:用于广播信息到当前主机 - 10.0.0.0/8:用于专用网络中的本地通信 - 172.16.0.0/12:用于专用网络中的本地通信 - 192.168.0.0/16:用于专用网络中的本地通信 - 100.64.0.0/10:用于在电信级NAT环境中服...","relativePath":"Tech/Network/网络层/Ipv4地址.md","rawContent":"# 1. 特殊ip地址汇总\r\n- 0.0.0.0/8:用于广播信息到当前主机\r\n- 10.0.0.0/8:用于专用网络中的本地通信\r\n- 172.16.0.0/12:用于专用网络中的本地通信\r\n- 192.168.0.0/16:用于专用网络中的本地通信\r\n- 100.64.0.0/10:用于在电信级NAT环境中服务提供商与其用户通信\r\n- 127.0.0.0/8:用于到本地主机的环回地址\r\n- 169.254.0.0/16:用于单链路的两个主机之间的链路本地地址,而没有另外指定IP地址,例如通常从DHCP服务器所检索到的IP地址\r\n- 192.0.0.0/24:用于IANA的IPv4特殊用途地址表\r\n- 192.0.2.0/24:分配为用于文档和示例中的“TEST-NET”(测试网),它不应该被公开使用\r\n- 192.88.99.0/24:用于6to4任播中继,已废弃\r\n- 198.18.0.0/15:用于测试两个不同的子网的网间通信\r\n- 198.51.100.0/24:分配为用于文档和示例中的“TEST-NET-2”(测试-网-2),它不应该被公开使用\r\n- 203.0.113.0/24:分配为用于文档和示例中的“TEST-NET-3”(测试-网-3),它不应该被公开使用\r\n- 224.0.0.0/4:用于多播\r\n- 240.0.0.0/4:用于将来使用\r\n- 255.255.255.255/32:用于受限广播地址"},{"id":"IPv6地址","title":"IPv6地址","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":5,"slug":"ipv6地址","description":"1. 地址格式 IPv6地址有128bit,在这128bit中,前64bit是网络前缀,后64bit是接口标识。在前64bit中,前48bit又是全球可汇总地址,在给一个公司分配IPv6地址时,总是分配给它一个前48bit固定的地址,而后面的16bit又可以被该公司用来做子网地址。这样分配,可以方便...","relativePath":"Tech/Network/网络层/IPv6地址.md","rawContent":"# 1. 地址格式\r\nIPv6地址有128bit,在这128bit中,前64bit是网络前缀,后64bit是接口标识。在前64bit中,前48bit又是全球可汇总地址,在给一个公司分配IPv6地址时,总是分配给它一个前48bit固定的地址,而后面的16bit又可以被该公司用来做子网地址。这样分配,可以方便做路由聚合。\r\n## 1.1. 前导零可以省略\r\n一段内可以省略连续的一个零或多个零\r\n2001:**0**db8:**0**2de:**0000**:**0000**:**0000**:**0000**:**0**e13\r\n2001:db8:2de:**0000**:**0000**:**0000**:**0000**:e13\r\n2001:db8:2de:**000**:**000**:**000**:**000**:e13\r\n2001:db8:2de:**00**:**00**:**00**:**00**:e13\r\n2001:db8:2de:**0**:**0**:**0**:**0**:e13\r\n## 1.2. 双冒号表示一组0或多组连续的零,但只能出现一次\r\n2001:db8:2de:**0**:**0**:**0**:**0**:e13\r\n2001:db8:2de::e13\r\n\r\n可以省略连续的多段零或只省略一段零\r\n2001:0db8:0000:0000:0000:0000:1428:57ab\r\n2001:0db8:0000:0000:0000::1428:57ab\r\n2001:0db8:0:0:0:0:1428:57ab\r\n2001:0db8:0::0:1428:57ab\r\n2001:0db8::1428:57ab\r\n\r\n## 1.3. IPv4 In IPv6\r\nIpv4位址可以很容易的转化为IPv6格式。举例来说,如果IPv4的一个地址为135.75.43.52(十六进制为0x874B2B34),它可以被转化为0000:0000:0000:0000:0000:FFFF:874B:2B34 或者::FFFF:874B:2B34。同时,还可以使用混合符号(IPv4-compatible address),则地址可以为::ffff:135.75.43.52。\r\n# 2. 地址分类\r\n\r\n# 3. 单播地址\r\n## 3.1. GUA\r\nGUA,global unicast address,全球单播地址,该类地址类似于IPv4中的公网地址。\r\n\r\n目前的GUA地址,前3bit固定为001,因此GUA地址范围为:\r\n\r\n```text\r\n2000:: —— 3FFF:FFFF:FFFF:FFFF:FFFF:FFFF\r\n```\r\n\r\nGUA地址一共占1/8的IPv6地址。\r\n\r\n## 3.2. ULA\r\nULA,unique local address,唯一本地地址,该地址类似于IPv4中的私网地址。ULA地址前7为固定,地址格式为:\r\n\r\n```text\r\nFC00::/7\r\n```\r\n \r\n因此,FC00:/8和FD00:/8都是ULA地址。一般来说,ULA地址只在网络内部使用,但是ULA在配置时,必须先申请一个40bit的Global ID,因此,基本上所有的ULA地址也不会重复,即使不小心将ULA地址发布到公网上去也不会引起太大问题。这一点和IPv4有极大的不同。\r\n\r\n## 3.3. LLA\r\nLLA,Link-Local Address,链路本地地址。该地址只在本地链路上有效,不能跨路由器路由设备。\r\n该地址地址格式为:\r\n```text\r\nFE80::/10\r\n```\r\n一般来说,在路由器上,该地址可以由运行IPv6的协议栈自动生成(根据网卡MAC地址),而在PC主机上,处于保护本地MAC地址的考虑,一般按照特殊的算法计算。\r\n\r\n## 3.4. 其他特殊地址\r\n此外,IPv6还有很多其他的特殊地址,比如`::/128`,该地址为未知地址,类似于IPv4的0.0.0.0,在DHCP阶段发送Discover数据包时会使用。再比如`::1/128`,该地址为本地地址,发往该地址的数据包不会出网卡,类似于IPv4的127.0.0.1,因此。PING该地址常被用来测试自己的PC是否支持IPv6协议栈。\r\n\r\n# 4. 组播地址\r\nIPv6组播地址为:FF00::/8,具体格式如下所示:\r\n\r\n| 8bit,全1 | 4bit, flags | 4bit, scope | Group ID, 112位 |\r\n| --------- | ----------- | ----------- | -------------- |\r\n| | | | |\r\n\r\n在IPv6组播中,flag占4bit,前3bit为0,最后bit如果为0,则表示永久性多播地址,如果为1,标识临时性多播地址。\r\n\r\n# 5. 任播地址\r\n除了上述地址外,IPv6还支持一种任播地址,IPv6任播地址与IPv6单播地址格式完全相同,没有任何区别,如果要配置一个地址是任播地址,则必须手工指名。\r\n\r\n在IPv6设计中,可以将多个站点配成相同的任播地址,目的地址是任播地址的数据包在转发时,会自动去往最近的站点。这样会起到负载分担和冗余备份的作用。特别适合用于当一个一个站点在多个地区部署CDN网络的类似情景。"},{"id":"网络层","title":"网络层","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"网络层","description":"","relativePath":"Tech/Network/网络层/网络层.md","rawContent":""},{"id":"链路本地地址","title":"链路本地地址","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"链路本地地址","description":"链路本地地址(Link-local Address)是一类特殊的IP地址,仅用于在网段内、同一广播域内的主机相互通信使用,这类主机可认为是不需要外部互联网服务的。其中IPv4的链路本地地址定义在169.254.0.0/16范围内,IPv6定义在fe80::/10范围。链路本地地址在ipv4和ipv6...","relativePath":"Tech/Network/网络层/链路本地地址.md","rawContent":"**链路本地地址**(Link-local Address)是一类特殊的IP地址,仅用于在网段内、同一广播域内的主机相互通信使用,这类主机可认为是不需要外部互联网服务的。其中IPv4的链路本地地址定义在169.254.0.0/16范围内,IPv6定义在fe80::/10范围。链路本地地址在ipv4和ipv6场景下有不同的使用场景。\r\n\r\n# 1. IPv4场景\r\nIPv4中通常只用于网络接口没有外部的、有状态的IP地址的情况下(比如没有DHCP服务或其他地址配置失效的情况),即各种外部获取IP地址的途径失败时,主机会随机在169.254.0.0范围内随机选择一个地址(除两端的169.254.0.0/16和169.254.255.0/16,用作保留),用随机选的地址进行ACD(冲突地址检测),直到发现一个不发生冲突的随机地址则将其使用。这也是无状态地址自动配置的一种,IPv4的这个可以称作APIPA(自动专用IP寻址)。因为伪随机数的生成与种子相关,种子不变化时伪随机数基本也是固定的,所以有的主机会使用MAC地址作为伪随机数的生成种子,降低同一网段内随机选取的链路本地地址在ACD后发现冲突的概率。\r\n\r\n# 2. IPv6场景\r\nIPv6中的链路本地地址的生成是强制的。即主机如果支持IPv6协议栈则强制性的需要生成一个链路本地地址,这与IPv6本身的设计有关。IPv4中有广播的概念,域内广播到达路由器时是不会被转发到其他网段的,但IPv6没有广播的概念,IPv6尽可能用组播来代替IPv4的这一特性。IPv6中使用链路本地地址表示该报文不需要被跨网段转发,其最常见的应用即在于邻居发现协议,IPv6的ND协议是用于替代IPv4的ARP协议的从协议地址向硬件地址映射的功能。所以某种角度上看IPv6的链路本地地址其实就是一个用在二层的协议地址,与主机的MAC地址一一对应,效果上也与IPv4的链路本地地址一致,即只能在本网段内通信,不考虑互联网服务,尽管这不是它最常见的用法。\r\n\r\nIPv6的链路本地地址需要强制生成,其生成方式与IPv4有显著区别,不是用随机生成方式获得,毕竟对于IPv6即使是定义了fe80的前缀,要在后面的数量宏大的地址内随机选地址再发起ACD显得有点多余。IPv6的链路本地地址以IID(接口标识符)为基础来分配,IID长度通常为64位,内容由主机的MAC地址按一定格式规则形成,常见的格式规则有EUI-48和EUI-64两种。EUI(扩展唯一标识符)由IEEE定义,EUI-48和EUI-64分别属于短格式和长格式,其开头24位都是OUI(组织唯一标识符),由IEEERA机构统一向全球厂商分配,后面的24位或40位由各个组织自行分配,显然EUI-64为组织提供更多的分配空间,但EUI-48还是很常用的。两种EUI格式中,第一个字节的低两位用于表示全局管理/本地管理、个人地址/组地址,分别叫做u位和g位。u位置1时表示该地址是本地管理的,否则是统一管理的(通常在互联网环境下);g位置1时表示该地址是组地址,否则是个人地址。在最终的IPv6链路本地地址中,EUI都是在低位的。\r\n\r\nEUI-48地址基本可以理解为就是48位的MAC地址,格式也一样,即前24位是OUI,后24位是组织自己分配的。EUI-48向EUI-64的转换则另有一些规则:首先前24位的OUI保留,然后紧接着的第4、5字节替换为FFFE,EUI-48的后24位再补到第6字节及以后,便得到EUI-64地址。从EUI-64得到IPv6的IID还需要将u位取反,最终得到的地址就是我们抓包时比较常见的那个链路本地地址。"},{"id":"BGP","title":"BGP","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"bgp","description":"","relativePath":"Tech/Network/路由协议/BGP/BGP.md","rawContent":""},{"id":"BGP基础概念","title":"BGP基础概念","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":11,"slug":"bgp基础概念","description":"1. AS - OSPF、IS-IS等IGP路由协议在组织机构网络内部广泛应用,随着网络规模扩大,网络中路由数量不断增长,IGP已无法管理大规模网络,AS的概念由此诞生。 - AS指的是在同一个组织管理下,使用统一选路策略的设备集合。 > [!question] 不同的AS之间需要进行通信,在AS之...","relativePath":"Tech/Network/路由协议/BGP/BGP基础概念.md","rawContent":"# 1. AS\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503192202966.png)\n- OSPF、IS-IS等IGP路由协议在组织机构网络**内部**广泛应用,随着网络规模扩大,网络中路由数量不断增长,IGP已无法管理大规模网络,AS的概念由此诞生。\n- AS指的是在同一个组织管理下,使用统一选路策略的设备集合。\n> [!question]\n不同的AS之间需要进行通信,在AS之间应该使用何种路由协议进行路由的传递?\n# 2. 什么是BGP\nBGP(Border Gateway Protocol,边界网关协议)是互联网上一个核心的**去中心化自治路由协议**。\n- 当网络过大的时候,会导致路由表过大而难以维护,这时候采用分治的方法,将一个**大网络划分为若干个小网络**,这些小网络称为自治系统(AS),BGP的诞生就是用于自治**系统间的通信**。\n- 它通过维护**IP路由表或前缀表**来实现自治系统(AS)之间的可达性,属于矢量路由协议。\n- 大多数互联网服务提供商必须使用BGP来与其他ISP创建路由连接,特大型的私有IP网络也可以使用BGP,将若干个大型的OSPF网络进行合并。\nBGP是**应用层协议**,其传输层使用TCP,默认端口号是**179**。TCP连接的窗口是65K字节,也就是说TCP连接允许在没有确认包的情况下,连续发送65K的数据。而其他的路由协议,例如OSPF的窗口只有一个数据包,也就是说前一个数据包收到确认包之后,才会发送下一个数据包。当网络规模巨大时,需要传输的数据也相应变大,这样效率是非常低的,这也是它不适合大规模网络的原因之一。而正是由于TCP可以可靠的传输大量数据,且互联网的路由信息是巨大的,TCP被选为BGP的传输层协议,并且BGP适合大规模网络环境。\n# 3. BGP与IGP\n## 3.1. 使用IGP传递路由\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503192211312.png)\n- AS之间需要直连链路,或通过VPN协议构造逻辑直连(例如GRE Tunnel)进行邻居建立。\n- AS之间可能是不同的机构、公司,相互之间无法完全信任,使用IGP可能存在暴露AS内部的网络信息的风险。\n- 整个网络规模扩大,路由数量进一步增加,路由表规模变大,路由收敛变慢,设备性能消耗加大。\n## 3.2. 使用BGP传递路由\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503192212190.png)\n考虑到IGP在AS间传递路由的各种问题,网络专家们发明了BGP(Border Gateway Protocol,边界网关协议)协议进行路由传递,相较于传统的IGP协议:\n- 【不需要直连链路】BGP基于TCP,只要能够建立TCP连接即可建立BGP。\n- 【不暴露网络结构】只传递路由信息,不参与路由发现和计算,不会暴露AS内的拓扑信息。\n- 【不需要周期性更新】触发式更新,而不是进行周期性更新。\n## 3.3. 核心区别\n### 3.3.1. 路由处理策略\nIGP核心在于发现和计算路由,BGP的核心在于通告和控制路由。IGP协议会互相通告自己的全部链接状态,交互的是整个网络拓扑信息,无法应对整个互联网错综复杂的网络结构。BGP并不关心AS内部的网络结构,只关心某段IP送到某个边界路由器上就行了。\n### 3.3.2. 是否中心化\nBGP是去中心化的。因为BGP交换的数据是AS之间的路由表,AS通常都是一个国家或者一个大型的运营商,AS之间的**地位是对等**的。一个AS是否要接入到另外一个AS,不需要更高层次的网络管理者的授权。而OSPF/ISIS就不行,OSPF/ISIS是中心化的,**OSPF需要area0交换数据,ISIS需要骨干区**,这些都是中心化的配置,如果国际互联网用OSPF/ISIS,那么就意味着很多数据流量就不得不通过某个中心区域,如果中心区域故障,那么全球互联网就瘫痪了。同时,**OSPF和ISIS在一个area里都需要一个核心路由器**,OSPF叫DR,ISIS叫DIS,尤其是在一个广播网络,DR/DIS非常重要,他们负责通告给其它路由器area里的路由表。有了核心路由器,OSPF/ISIS才能计算出当前arae的拓扑结构。但BGP不行,如果BGP里有类似DR/DIS的角色,那么这个网络就不再是去中心化的了,DR/DIS的选举和变化会导致网络波动,对于BGP这种拥有大规模路由表(上万条)的协议来说,这种波动的影响会非常巨大。去中心化意味着BGP的每个路由器,只能自己算自己的路由表,如果BGP也负责计算到peer端的路由,那么每个BGP路由器计算出来的结果是没办法统一的(没有DR/DIS),所以BGP为了去中心化,放弃了计算到peer路由的过程,把这个工作交给了IGP来实现。\n### 3.3.3. 协议开销\n**IGP协议报文开销太大**。BGP的报文格式中,是以属性为单位进行通告的,`属性+路由条目*N`的这种格式。OSPF的LSA和ISIS的LSP都是带有一定链接状态的数据,格式相当于`(路由条目+属性)*N`的格式,如果路由表数量巨大,那么OSPF/ISIS需要非常多的通信才能完成路由交换。如果是外部路由,那么为了描述一条路由信息,OSPF/ISIS需要更长的报文。同时OSPF/ISIS,**都存在老化时间**,需要周期性的刷新,BGP不需要。一个国家级别的网络,路由表的规模可能是上万条的,全球互联网的路由表可能是几十万甚至上百万条的,这种情况下,使用OSPF/ISIS的话,性能就不太好了。\n### 3.3.4. 网络设计目标\n**BGP不关注网络内部的可达性**。这种设计主要是为了“**过境流量**”。举例:中国到美国的数据流量,主要走中美海底光缆。但如果这条光缆中断了,那么,还可以走中国-日本-美国;中国-欧洲-美国;中国-新加坡-美国……那么当数据流量在日本、欧洲、新加坡中转时,BGP协议不关注这些过境流量如何通过内部网络,换句话说,中国的路由器上,不需要知道日本、欧洲、新加坡的内部网络的路由,只需要知道边界在哪里,边界是否可达即可。BGP的这种特性还可以用来做网络调优。购买VPS/虚拟主机服务的时候,肯定听说过BGP机房的说法,所谓的BGP机房,就是在机房的出口处,配置一台运行IBGP的路由器,直接连通到EBGP的出口,那么对于跨AS的访问,只需要一跳就可以进入另外一个AS,不需要在运营商网络内部绕路。\n### 3.3.5. 邻居建立\nBGP有别于IGP的一大特点是他的对等体或者说邻居建立,是不需要直连的,ospf,eigrp这些邻居建立都必须直连,而bgp两个邻居只要update地址三层可达即可。可别小看这个特点,这个在mpls vpn的控制层面上跨iP设备传递vpnv4路由,以及在mpls域间option c方案中都是必须利用到的特点。\n### 3.3.6. BGP路径属性丰富\nbgp有强大的路由操控性,具有**多种选路原则**。本地优先级,as号长度,origin属性,med等等原则。\nIS-IS、OSPF只能通过Cost控制路径选择\n### 3.3.7. 拓展性\nbgp有丰富的拓展性,支持ipv4,ipv6,vpnv4,vpnv6,evpn,vpls等等,在运营商,数据中心,校园网,企业网里都有丰富的使用场景。而ospf这种就完全做不到了,甚至像ipv6这种,都必须基于ospfv3才能实现了。\n# 4. BGP对等体\nBGP有两种邻居\n- **IBGP(Interior BGP)**,运行在同一个AS内的BGP路由器之间的BGP邻接关系。\n- **EBGP(Exterior BGP)**,运行在不同AS间的BGP路由器之间的BGP对等关系,必须满足以下两个条件:\n\t1. 两个路由器所属AS不同\n\t2. peer命令指定的对等体IP地址要求路由可达,并且TCP连接能够正确建立\n\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427213611.png)\n"},{"id":"BGP邻居建立","title":"BGP邻居建立","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"bgp邻居建立","description":"- 先启动BGP的一端先发起TCP连接,R1先启动BGP,R1使用随机端口号向R2的179端口发起TCP连接,完成TCP连接的建立。 - 三次握手建立完成之后,R1、R2之间相互发送Open报文,携带参数用于对等体建立 - 参数协商正常之后双方相互发送Keepalive报文,收到对端发送的Keepa...","relativePath":"Tech/Network/路由协议/BGP/BGP邻居建立.md","rawContent":"![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503192234972.png)\n- 先启动BGP的一端先发起TCP连接,R1先启动BGP,R1使用随机端口号向R2的179端口发起TCP连接,完成TCP连接的建立。\n- 三次握手建立完成之后,R1、R2之间相互发送Open报文,携带参数用于对等体建立\n- 参数协商正常之后双方相互发送Keepalive报文,收到对端发送的Keepalive报文之后对等体建立成功,同时双方定期发送Keepalive报文用于保持连接。\n# 1. TCP连接\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503192241218.png)\n- 缺省情况下,BGP使用报文出接口作为TCP连接的本地接口。\n- 在部署IBGP对等体关系时,建议使用Loopback地址作为更新源地址。Loopback接口非常稳定,而且可以借助AS内的IGP和冗余拓扑来保证可靠性。\n- 在部署EBGP对等体关系时,通常使用直连接口的IP地址作为源地址,如若使用Loopback接口建立EBGP对等体关系,则应注意EBGP多跳问题。\n> [!note]\n> Loopback地址和lo0(127.0.0.1,本地回环地址)是不一样的,路由器的Loopback地址是一个虚拟接口,通常配置为一个可路由的IP地址,例如10.0.12.1或192.168.0.1等等。除了Loopback接口外,路由器上还有很多连接到不同的网段的多个以太网接口,每个接口都有自己的IP地址。\n\n故障隔离\n> [!note]\n> 一般而言在AS内部,网络具备一定的冗余性。在R1与R3之间,如果采用直连接口建IBGP邻居关系,那么一旦接口或者直连链路发生故障,BGP会话也就断了,但是事实上,由于冗余链路的存在,R1与R3之间的IP连通性其实并没有DOWN(仍然可以通过R4到达彼此)。**如果用 Loopback 建邻居:** 只要 A 和 B 之间还有**任何**一条物理路径能通,Loopback 的 IP 就能互相 Ping 通,BGP 会话就不会中断。\n# 2. Open报文\n主要包括\n- My Autonomous System:自身AS号\n- Hold Time:用于协商后续Keepalive报文发送时间\n- BGP Identifier:自身Router ID\n> [!tip]\n> BGP建立对等体的对等体都会发起TCP三次握手,所以会建立两个TCP连接,但是实际BGP只会保留其中一个TCP连接,从Open报文中获取对端BGP Identifier之后BGP对等体会比较本端的Router ID和对端的Router ID大小,如果本端Router ID小于对端Router ID,则会关闭本地建立的TCP连接,使用由对端主动发起创建的TCP连接进行后续的BGP报文交互。\n\n# 3. Update报文\n\n[![image-20220928134909305](https://img2023.cnblogs.com/blog/1661734/202309/1661734-20230918095024034-1658400849.png)](https://img2023.cnblogs.com/blog/1661734/202309/1661734-20230918095024034-1658400849.png)\n\nBGP对等体关系建立之后,BGP路由器发送BGP Update(更新)报文通告路由到对等体。路由通告包括可达路由和不可达路由。\n# 4. Notification报文\nNotification报文用于错误信息通告,然后断开BGP邻居\n# 5. Route-Refresh报文\n用于请求对等体重新发送路由信息\n# 6. 实验\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503192334970.png)\n\n## 6.1. 配置端口\nR1\n```shell\nAR2#configure terminal\nAR2(config)#interface f0/0\nAR2(config-if)#ip address 192.168.1.2 255.255.255.0\nAR2(config-if)#no shutdown\nAR2(config-if)#interface f1/0\nAR2(config-if)#ip address 10.0.0.1 255.0.0.0\nAR2(config-if)#no shutdown\nAR2(config-if)#interface lo0\nAR2(config-if)#ip address 2.2.2.2 255.255.255.255\n```\nR2\n```shell\nR2#configure terminal\nEnter configuration commands, one per line. End with CNTL/Z.\nR2(config)#interface f1/0\nR2(config-if)#ip address 192.168.1.1 255.255.255.0\nR2(config-if)#no shutdown\nR2(config-if)#interface\nR2(config-if)#interface f0/0\nR2(config-if)#ip address 192.168.2.1 255.255.255.0\nR2(config-if)#no shutdown\nR2(config-if)#interface lo0\nR2(config-if)#ip address 1.1.1.1 255.255.255.255\n```\nPC2\n```shell\nPC2> ip 10.0.0.2 255.0.0.0 10.0.0.1\n```\nR3\n```shell\nR3#configure terminal\nR3(config)#interface f0/0\nR3(config-if)#ip address 192.168.3.1 255.255.255.0\nR3(config-if)#no shutdown\nR3(config-if)#interface f1/0\nR3(config-if)#ip address 192.168.2.2 255.255.255.0\nR3(config-if)#no shutdown\nR3(config-if)#interface lo0\nR3(config-if)#ip address 3.3.3.3 255.255.255.255\n```\nR4\n```shell\nR4#configure terminal\nR4(config)#interface f1/0\nR4(config-if)#ip address 192.168.3.2 255.255.255.0\nR4(config-if)#no shutdown\nR4(config-if)#interface f0/0\nR4(config-if)#ip address 11.0.0.1 255.0.0.0\nR4(config-if)#no shutdown\nR4(config-if)#interface lo0\nR4(config-if)#ip address 4.4.4.4 255.255.255.255\n```\nPC1\n```shell\nPC1> ip 11.0.0.2 255.0.0.0 11.0.0.1\n```\n## 6.2. R1和R2配置OSPF\nR2\n```shell\nR2#configure terminal\nR2(config)#router ospf 100\nR2(config-router)#network 192.168.1.0 0.0.0.255 area 0\nR2(config-router)#network 1.1.1.1 0.0.0.0 area 0\n```\nR1\n```shell\nR1#configure terminal\nR1(config)#router ospf 100\nR1(config-router)#network 10.0.0.0 0.255.255.255 area 0\nR1(config-router)#network 2.2.2.2 0.0.0.0 area 0\n```\n## 6.3. R3和R4配置RIP\n\n## 6.4. 配置BGP"},{"id":"路由协议","title":"路由协议","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"路由协议","description":"","relativePath":"Tech/Network/路由协议/路由协议.md","rawContent":""},{"id":"CPU","title":"CPU","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"cpu","description":"1. 基本概念 - Socket: 指的是主板上用于安装物理CPU芯片的接口。每一个这样的接口能够插入一个物理的CPU处理器,一台计算机可以配备多个这样的插槽,进而安装多个物理CPU,以增强系统的计算能力和性能。一个物理CPU可以有多个物理CPU核。 - 物理核(): 可以看的到的,真实的cpu核,...","relativePath":"Tech/Os/Linux/CPU.md","rawContent":"# 1. 基本概念\r\n- Socket: 指的是主板上用于安装物理CPU芯片的接口。每一个这样的接口能够插入一个物理的CPU处理器,一台计算机可以配备多个这样的插槽,进而安装多个物理CPU,以增强系统的计算能力和性能。一个物理CPU可以有多个物理CPU核。\r\n- 物理核(`physical core/processor`): 可以看的到的,真实的cpu核,有独立的电路元件以及L1,L2缓存,可以独立地执行指令。\r\n- 逻辑核( `logical core/processor`,LCPU): 在同一个物理核内,逻辑层面的核。(比喻,像动画片一样,我们看到的“动画”,其实是一帧一帧静态的画面,24帧/s连起来就骗过了人类的眼睛,看起来像动起来一样。逻辑核也一样,物理核通过高速运算,让应用程序以为有两个cpu在运算)。\r\n- 超线程( `Hyper-threading`, HT):超线程可以在一个逻辑核等待指令执行的间隔(等待从cache或内存中获取下一条指令),把时间片分配到另一个逻辑核。高速在这两个逻辑核之间切换,让应用程序感知不到这个间隔,误认为自己是独占了一个核。\r\n\r\n> [!tip]\r\n> 一个CPU可以有多个物理核。如果开启了超线程,一个物理核可以分成n个逻辑核,n为超线程的数量。\r\n\r\n下图中有两个物理核,每个核有两个超线程(逻辑核),其中0-3为物理核,而8-11是物理核通过超线程技术模拟出来的逻辑核心。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202503202314672.png)\r\n\r\n# 2. lscpu命令\r\n```shell\r\nlscpu\r\n```\r\n- `CPU(s)`:表示逻辑CPU的总数。\r\n- `Core(s) per socket`:每个 CPU 插槽的物理核心数。\r\n- `Socket(s)`:CPU 插槽的数量。\r\n- `Thread(s) per core`:每个物理核心的线程数。\r\n物理核心数 = `Core(s) per socket` × `Socket(s)`,下图为2 × 1 = 2\r\n逻辑核心数 = 物理核心数 × Thread(s) per coce,下图为2 x 2 = 4\r\n\r\n示例:\r\n```\r\nCPU(s): 32\r\nCore(s) per socket: 8\r\nSocket(s): 2\r\nThread(s) per core: 2\r\n```\r\n- `Socket(s): 2`:表明主板上有2个CPU插槽,并且每个插槽都安装了物理CPU。\r\n- `Core(s) per socket: 8`:意味着每个物理CPU含8个物理核心。\r\n- 综合这两个信息,我们可以算出系统中物理核心的总数为 16(8 核心/插槽 × 2 插槽)。\r\n- `Thread(s) per core: 2` 表示每个物理核心有2个线程,结合前面的物理核心总数,可知逻辑 CPU 的总数为 32(16 物理核心 × 2 线程/核心),这与 `CPU(s): 32 相吻合。\r\n# 3. /proc/cpuinfo文件\r\n```shell\r\ngrep 'physical id' /proc/cpuinfo | sort -u | wc -l # 物理CPU个数\r\ngrep 'core id' /proc/cpuinfo | sort -u | wc -l # 物理核心数\r\ngrep 'processor' /proc/cpuinfo | wc -l # 逻辑核心数\r\n```\r\n"},{"id":"file system","title":"File System","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"file-system","description":"","relativePath":"Tech/Os/Linux/file system/file system.md","rawContent":""},{"id":"常见文件目录","title":"常见文件目录","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":8,"slug":"常见文件目录","description":"各大发行版基本一致,遵循的是通用的FHS(Filesystem Hierarchy Standard)约定,(个别子目录如 lib64、multiarch 名称可能略有不同) 1. 开发者部署模板 用一个假想应用myapp为例,展示其在Linux系统中应该如何分布文件和目录。 🏗️ myapp 部...","relativePath":"Tech/Os/Linux/file system/常见文件目录.md","rawContent":"各大发行版基本一致,遵循的是通用的FHS(Filesystem Hierarchy Standard)约定,(个别子目录如 lib64、multiarch 名称可能略有不同)\r\n# 1. 开发者部署模板\r\n用一个假想应用myapp为例,展示其在Linux系统中应该如何分布文件和目录。\r\n 🏗️ myapp 部署模板(FHS 标准 + systemd)\r\n```\r\n # 核心可执行文件\r\n/usr/local/bin/myapp # 主程序(编译/安装好的二进制)\r\n\r\n# 配置文件(小而精,可编辑)\r\n/etc/myapp/\r\n├─ config.yml # 主配置\r\n└─ logging.conf # 日志配置 (如 log4j, logback, python logging)\r\n\r\n# systemd 单元文件(管理启动/停止)\r\n/etc/systemd/system/myapp.service # 自定义服务单元\r\n\r\n# 持久化数据(需备份) \r\n/var/lib/myapp/\r\n├─ db.sqlite # 内置数据库(如 SQLite)\r\n└─ state.json # 应用运行状态、索引文件\r\n\r\n# 日志(需轮转管理 logrotate/journald)\r\n/var/log/myapp/\r\n├─ myapp.log # 主日志\r\n└─ error.log # 错误日志\r\n\r\n# 缓存(可删除,会重建)\r\n/var/cache/myapp/\r\n├─ tmp_result/ # 临时计算结果\r\n└─ images/ # 下载或生成的图片缓存\r\n\r\n# 运行时文件(随重启清空)\r\n/run/myapp/\r\n├─ myapp.pid # 进程 PID 文件\r\n└─ myapp.sock # Unix socket (如 FastCGI/IPC)\r\n\r\n# 临时文件(不跨重启)\r\n/tmp/myapp.* # 中间数据、临时输出\r\n```\r\n# 2. 精简版\r\n```\r\n/\r\n├─ ⚙️ etc/ # 系统配置(小而精)\r\n│ └─ 📄 / # 你的应用配置 (config.yml, app.conf)\r\n│\r\n├─ 🏠 home/ # 用户根目录\r\n│ └─ 👤 username/.local/ # 用户级安装 (bin, lib, share, config, cache)\r\n│\r\n├─ 👑 root/ # root 用户根目录\r\n│\r\n├─ 📦 usr/ # 系统安装的软件(包管理器管理)\r\n│ ├─ 🛠️ bin/ # 普通可执行程序 (gcc, git)\r\n│ ├─ 🔧 sbin/ # 管理命令\r\n│ ├─ 📚 lib/ # 系统库\r\n│ ├─ 📖 share/ # 数据资源 (man, icons, locale)\r\n│ └─ 📦 local/ # 本机编译安装的软件\r\n│ ├─ 🛠️ bin/ # 手工编译安装的可执行文件\r\n│ ├─ 📚 lib/ # 本机库\r\n│ ├─ 📄 etc/ # 本机配置\r\n│ └─ 📖 share/ # 本机数据资源\r\n│\r\n├─ 📦 opt/ # 第三方大软件(自带依赖)\r\n│ └─ 📂 // # 整个应用目录 (bin, lib, config, static)\r\n│\r\n├─ 🔄 var/ # 变动数据(日志、缓存、状态)\r\n│ ├─ 📝 log// # 应用日志\r\n│ ├─ 💾 lib// # 数据库、持久化状态\r\n│ ├─ 🗂️ cache// # 缓存文件\r\n│ └─ 📬 spool// # 队列数据 (邮件、打印、任务)\r\n│\r\n├─ 🚀 run/ # 运行时状态(随重启清空)\r\n│ └─ 📄 /{pid,sock} # PID 文件、socket\r\n│\r\n└─ 🗑️ tmp/ # 临时文件(重启清空)\r\n └─ 🗂️ .* # 中间结果、临时缓存\r\n```\r\n# 3. 详细版\r\n```\r\n/\r\n├─ 🛠️ bin/ # 基本用户命令 (ls, cp, mv)\r\n├─ 🔧 sbin/ # 系统管理命令 (reboot, iptables)\r\n├─ 💻 boot/ # 引导文件 (内核 vmlinuz, grub)\r\n├─ 📀 dev/ # 设备文件 (/dev/sda, /dev/tty)\r\n├─ ⚙️ etc/ # 系统配置 (/etc/hosts, passwd, fstab)\r\n│ ├─ 🌐 network/ # 网络配置\r\n│ ├─ 🔄 systemd/ # systemd 配置\r\n│ └─ 🖥️ X11/ # 图形界面配置\r\n├─ 🏠 home/ # 用户家目录 (/home/user)\r\n│ ├─ 👤 user1/\r\n│ └─ 👤 user2/\r\n├─ 👑 root/ # root 用户家目录\r\n├─ 📚 lib/ # 系统库\r\n├─ 📚 lib64/ # 64 位系统库\r\n├─ 💽 media/ # 移动介质挂载点 (U盘, 光驱)\r\n├─ 📂 mnt/ # 临时挂载点\r\n├─ 📦 opt/ # 第三方/大型软件 (/opt/google)\r\n├─ 🔍 proc/ # 内核与进程信息 (/proc/cpuinfo)\r\n├─ 🚀 run/ # 运行时状态 (PID, socket)\r\n├─ 🌐 srv/ # 服务数据目录 (网站, FTP)\r\n├─ 🧩 sys/ # 硬件与内核信息 (/sys/class)\r\n├─ 🗑️ tmp/ # 临时文件 (重启清空)\r\n├─ 📦 usr/ # 用户应用和库 (绝大部分软件)\r\n│ ├─ 🛠️ bin/ # 用户命令 (gcc, python)\r\n│ ├─ 🔧 sbin/ # 管理命令 (apachectl)\r\n│ ├─ 📚 lib/ # 共享库\r\n│ ├─ 📑 include/ # 头文件\r\n│ ├─ 📖 share/ # 架构无关数据 (man, locale, icons)\r\n│ └─ 📦 local/ # 本机安装软件 (结构同 /usr)\r\n│ ├─ 🛠️ bin/\r\n│ ├─ 📚 lib/\r\n│ ├─ ⚙️ etc/\r\n│ └─ 📖 share/\r\n├─ 🔄 var/ # 变动数据 (日志, 缓存, DB)\r\n│ ├─ 📝 log/ # 日志文件\r\n│ ├─ 💾 lib/ # 服务持久化数据 (DB, 索引)\r\n│ ├─ 🗂️ cache/ # 缓存 (apt, yum)\r\n│ ├─ 📬 spool/ # 队列 (邮件, 打印, cron)\r\n│ ├─ 🗑️ tmp/ # 跨重启的临时文件\r\n│ └─ 🚀 run -> /run # 兼容符号链接\r\n└─ 🛟 lost+found/ # 文件系统修复时的残片\r\n```\r\n\r\n- `/bin`:bin是Binary的缩写, 这个目录存放着最经常使用的命令。👉 **不要放自家程序**。发行版维护\r\n- `/sbin`:s就是Super User的意思,这里存放的是系统管理员使用的系统管理程序.`/sbin -> usr/sbin`。👉 **不要放自家程序**。发行版维护\r\n- `/usr`:这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于windows下的program files目录。👉 **打包分发(deb/rpm)时**:把可执行文件放 `/usr/bin`,库放 `/usr/lib*`,数据放 `/usr/share/`,头文件放 `/usr/include`(若公开开发包)。 \r\n\t- `/usr/bin`:系统用户使用的应用程序。\r\n\t- `/usr/sbin`:超级用户使用的比较高级的管理程序和系统守护程序。\r\n\t- `/usr/src`:内核源代码默认的放置目录。\r\n\t- `/usr/lib*`:共享库\r\n - `/usr/share`:与架构无关的数据(man、示例、icons、locale)\r\n - `/usr/include`:头文件。 \r\n - `/usr/local`:**本机手工安装**的软件镜像 `/usr` 结构,避免与发行版冲突。 \r\n\t - 👉 自行编译/安装的工具放这里:`/usr/local/bin`、`/usr/local/lib*`、`/usr/local/share/`。 好处:一键清理、区分“官方包 vs 本机安装”。\r\n- `/opt/`:这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下,默认是空的。自包含的大型或第三方应用(如 `opt/vendor/app`),内部自带libs、runtime。 👉 商业软件、Node/Python 整个应用目录、前后端一体分发可放 `/opt//`;再**符号链接**主可执行到 `/usr/local/bin/` 便于 PATH使用。\r\n- `/etc`:这个目录用来存放所有的系统管理所需要的配置文件和子目录。你的应用的系统级默认配置:`/etc//config.yml`(只存配置,不存数据/密钥库)。 systemd 配置覆写放 `/etc/systemd/system/.service`(或 drop-in:`.d/*.conf`)。\r\n- `/var`:这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下,包括各种日志文件。 👉 你的应用把**日志/状态/缓存**分别放在对应子目录。确保权限与轮转(logrotate 或 journald)。\r\n\t- `/var/log//`:日志\r\n\t- `/var/lib//`:持久化状态(DB、索引、migrations 标记等)\r\n\t- `/var/cache//`:可丢弃缓存\r\n\t- `/var/spool//`:排队/待处理数据(邮件、打印、任务队列)\r\n\t- `/var/tmp/`:跨重启保留的临时文件\r\n- `/run/`(早期叫 `/var/run`):**运行时状态**(tmpfs,随重启清空)。 👉 PID 文件、Unix 套接字、锁文件、短期运行时数据:`/run//{app.pid,app.sock}`。\r\n- `/tmp`:这个目录是用来存放一些临时文件的(常在重启时清除)。 👉 临时中间文件、下载碎片等;**不**存需要保留的内容。 需要跨重启临时数据就用 `/var/tmp`。\r\n- `/srv/`:该目录存放一些服务启动之后需要提取的数据,如服务对外提供的数据根(如网站、FTP)。 👉 如你要维护“站点根目录”:`/srv/www//`。\r\n- `/home`:用户的主目录,在Linux中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的。👉 用户级安装与配置:\r\n\t- `~/.local/bin`:个人可执行(加入 PATH) \r\n\t- `~/.config//`:用户配置(XDG)\r\n\t- `~/.cache//`:用户缓存\r\n\t- `~/.local/share//`:用户数据\r\n- `/lib`及`/lib64/`:这个目录里存放着系统最基本的动态连接共享库,其作用类似于Windows里的DLL文件。几乎所有的应用程序都需要用到这些共享库。`/lost+found`:这个目录一般情况下是空的,当系统非法关机后,这里就存放了一些文件。\r\n- `/media`:linux系统会自动识别一些设备,例如U盘、光驱等等,当识别后,linux会把识别的设备挂载到这个目录下。\r\n- `/mnt`:系统提供该目录是为了让用户临时挂载别的文件系统的,我们可以将光驱挂载在/mnt/上,然后进入该目录就可以查看光驱里的内容了。\r\n- `/dev`:dev是Device(设备)的缩写, 该目录下存放的是Linux的外部设备,在Linux中访问设备的方式和访问文件的方式是相同的。\r\n- `/proc`:这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。这个目录的内容不在硬盘上而是在内存里,我们也可以直接修改里面的某些文件\r\n- `/boot`:这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。\r\n- `/sys`:这是linux2.6内核的一个很大的变化。该目录下安装了2.6内核中新出现的一个文件系统 sysfs 。sysfs文件系统集成了下面3种文件系统的信息:针对进程信息的proc文件系统、针对设备的devfs文件系统以及针对伪终端的devpts文件系统。该文件系统是内核设备树的一个直观反映。当一个内核对象被创建的时候,对应的文件和目录也在内核对象子系统中被创建。\r\n- `/root`:该目录为系统管理员,也称作超级权限者的用户主目录。\r\n- `/selinux`:这个目录是Redhat/CentOS所特有的目录,Selinux是一个安全机制,类似于windows的防火墙,但是这套机制比较复杂,这个目录就是存放selinux相关的文件的。\r\n\r\n\r\n\r\n"},{"id":"Linux","title":"Linux","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"linux","description":"","relativePath":"Tech/Os/Linux/Linux.md","rawContent":""},{"id":"bridge","title":"Bridge","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":5,"slug":"bridge","description":"1. 常见命令 2. 泛洪 - 泛洪机制:桥接设备会将该数据包从除接收端口之外的所有其他端口发送出去。这样做的目的是确保目的设备无论连接到哪个端口,都有机会接收到这个数据包。例如,在一个包含多个主机的局域网中,如果主机 A 向一个桥接设备发送了一个数据包,而该桥接设备的 FDB 表中没有目的主机的 ...","relativePath":"Tech/Os/Linux/Network/bridge.md","rawContent":"# 1. 常见命令\r\n```shell\r\nbrctl add br0\r\nbrctl addif br0 ethx\r\nbrctl showmacs br0\r\nbrctl show br0\r\n```\r\n# 2. 泛洪\r\n- **泛洪机制**:桥接设备会将该数据包从除接收端口之外的所有其他端口发送出去。这样做的目的是确保目的设备无论连接到哪个端口,都有机会接收到这个数据包。例如,在一个包含多个主机的局域网中,如果主机 A 向一个桥接设备发送了一个数据包,而该桥接设备的 FDB 表中没有目的主机的 MAC 地址,那么桥接设备会将这个数据包转发到除了连接主机 A 的端口之外的所有其他端口。\r\n- **学习过程**:在泛洪的同时,桥接设备会学习源 MAC 地址与接收端口的对应关系,并将其添加到 FDB 表中。这样当下次有发往该源 MAC 地址的数据包时,就可以直接根据 FDB 表进行转发,而不需要再泛洪。\r\n- **老化机制**:FDB 表中的条目通常有一个老化时间。如果在一段时间内没有再次收到来自某个 MAC 地址的数据包,对应的表项会被删除。这确保了 FDB 表不会因为长时间未使用的条目而变得臃肿,从而保证桥接设备的高效运行。\r\n# 3. iptables与bridge关系\r\n在 Linux 系统中,网桥(如`br0`)用于连接多个网络接口,在数据链路层(OSI 模型第 2 层)进行数据包转发,类似于硬件交换机的功能。而`iptables`是基于网络层(OSI 模型第 3 层)和传输层(OSI 模型第 4 层)的防火墙工具,用于根据设定的规则过滤和处理数据包。默认情况下,当`net.bridge.bridge-nf-call-iptables`参数的值为`1`(开启状态)时,内核会将桥接的数据包传递给`iptables`进行处理,`iptables`可以根据规则对这些数据包进行过滤、NAT(网络地址转换)等操作。当执行 `sysctl -w net.bridge.bridge-nf-call-iptables=0` 后,内核不再将桥接的数据包提交给`iptables`进行处理。这意味着,无论`iptables`中针对桥接接口(如`br0`)设置了何种规则,都不会对桥接网络中的数据包生效。例如,如果你之前设置了 `iptables -A FORWARD -i br0 -j DROP` 以阻止通过网桥转发的所有数据包,但执行上述命令后,这些数据包将不再受此规则影响,会直接通过网桥进行转发。\r\n\r\n- **INPUT 链**:如果包的目的IP是网桥所在主机自身的 IP 地址,且主机开启了`iptables`的INPUT链规则,那么这些包会经过INPUT链进行规则匹配。例如,当你在网桥上设置了IP地址,并且希望允许或拒绝特定源IP对该网桥 IP 的访问,就可以通过 INPUT 链规则来实现。假设网桥`br0`的 IP 为`192.168.1.1`,若 INPUT 链有规则`iptables -A INPUT -i br0 -p tcp --dport 80 -j DROP`,则从`br0`进入且目的端口为80的TCP包会被丢弃。\r\n- **FORWARD 链**:对于通过网桥转发的包(即源IP和目的IP都不是网桥所在主机的 IP),如果主机充当路由器功能且`iptables`的FORWARD链开启规则,这些包会经过 FORWARD 链。比如,在一个双网卡主机中,一个网卡连接外网,另一个网卡通过网桥连接内网,当内网主机访问外网时,数据包会经过网桥转发,此时若FORWARD链设置了`iptables -A FORWARD -i br0 -o eth0 -j ACCEPT`,则从`br0`进入并从`eth0`出去的包会被允许通过。 \r\n\r\n\r\n"},{"id":"Geneve隧道配置","title":"Geneve隧道配置","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":7,"slug":"geneve隧道配置","description":"1. geneve协议格式 GENEVE与VXLAN类似,仍然是Ethernet over UDP,也就是用UDP封装Ethernet。VXLAN header是固定长度的(8个字节,其中包含24bit VNI),与VXLAN不同的是,GENEVE header中增加了TLV(Type-Length...","relativePath":"Tech/Os/Linux/Network/geneve/Geneve隧道配置.md","rawContent":"# 1. geneve协议格式\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250106234908676.png)\r\nGENEVE与VXLAN类似,仍然是Ethernet over UDP,也就是用UDP封装Ethernet。VXLAN header是固定长度的(8个字节,其中包含24bit VNI),与VXLAN不同的是,GENEVE header中增加了TLV(Type-Length-Value),由8个字节的固定长度和0~252个字节变长的TLV组成。GENEVE header中的TLV代表了可扩展的元数据。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250107000425787.png)\r\n- Version(2bit):目前是0\r\n- Opt Len(6bit):以4字节为单位,表明Variable Length Options的长度。因为只有6bit,所以Variable Length Options最多是252(63*4)字节。\r\n- O(1bit):表明这是一个OAM包,包含了控制信息,而非数据。Endpoint可以根据这个bit来优先处理这个包。\r\n- C(1bit):表明在Variable Length Options里面,存在一个或者多个Critical的option。当C被置位时,Variable Length Options必须被解析,如果当前Endpoint不支持GENEVE解析,那么应该丢弃数据包。如果C没有被置位,那么Endpoint可以根据Opt Len直接丢弃所有的Variable Length Options。\r\n- Rsvd.(6bit):保留字段。\r\n- Protocol Type(16bit):被封装的协议类型,例如Ethernet就是0x6558。这个字段的存在,使得GENEVE封装其他的二层协议成为可能。\r\n- VNI(24bit):VNI。\r\n- Reserved(8bit):保留字段。\r\n- pVariable Length Options:由TLV构成,包含了可扩展的元数据。\r\n# 2. 基础实验\r\n宿主机添加geneve口,配置路由下一跳指向geneve口\r\n```shell\r\n# host a with ip 192.168.0.251\r\nsudo ip link add name geneve0 type geneve id 1000 remote 10.0.0.250\r\nsudo ip link set geneve0 up\r\nsudo ip addr add 11.200.1.1/32 dev geneve0\r\nsudo ip route add 11.200.2.1/32 dev geneve0\r\n\r\n# host b with ip 192.168.0.250\r\nsudo ip link add name geneve0 type geneve id 1000 remote 10.0.0.251\r\nsudo ip link set geneve0 up\r\nsudo ip addr add 11.200.2.1/32 dev geneve0\r\nsudo ip route add 11.200.1.1/32 dev geneve0\r\n```\r\nhost a抓包\r\n```shell\r\nroot@iZbp13m4rux6j370521ygbZ:~# tcpdump udp port 6081 -nne -vvvv\r\ntcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes\r\n22:47:57.358107 ee:ff:ff:ff:ff:ff > 00:16:3e:13:d7:e3, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 32272, offset 0, flags [none], proto UDP (17), length 134)\r\n 10.0.0.250.13206 > 10.0.0.251.6081: [no cksum] Geneve, Flags [none], vni 0x3e8, proto TEB (0x6558)\r\n\tf6:ed:db:8e:76:6e > 76:4d:d9:e8:20:34, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 53977, offset 0, flags [DF], proto ICMP (1), length 84)\r\n 11.200.2.1 > 11.200.1.1: ICMP echo request, id 4534, seq 45, length 64\r\n22:47:57.358154 00:16:3e:13:d7:e3 > ee:ff:ff:ff:ff:ff, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 15677, offset 0, flags [none], proto UDP (17), length 134)\r\n 10.0.0.251.50482 > 10.0.0.250.6081: [no cksum] Geneve, Flags [none], vni 0x3e8, proto TEB (0x6558)\r\n\t76:4d:d9:e8:20:34 > f6:ed:db:8e:76:6e, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 18896, offset 0, flags [none], proto ICMP (1), length 84)\r\n 11.200.1.1 > 11.200.2.1: ICMP echo reply, id 4534, seq 45, length 64\r\n22:47:58.382102 ee:ff:ff:ff:ff:ff > 00:16:3e:13:d7:e3, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 32583, offset 0, flags [none], proto UDP (17), length 134)\r\n 10.0.0.250.13206 > 10.0.0.251.6081: [no cksum] Geneve, Flags [none], vni 0x3e8, proto TEB (0x6558)\r\n\tf6:ed:db:8e:76:6e > 76:4d:d9:e8:20:34, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 54651, offset 0, flags [DF], proto ICMP (1), length 84)\r\n 11.200.2.1 > 11.200.1.1: ICMP echo request, id 4534, seq 46, length 64\r\n22:47:58.382151 00:16:3e:13:d7:e3 > ee:ff:ff:ff:ff:ff, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 16143, offset 0, flags [none], proto UDP (17), length 134)\r\n 10.0.0.251.50482 > 10.0.0.250.6081: [no cksum] Geneve, Flags [none], vni 0x3e8, proto TEB (0x6558)\r\n\t76:4d:d9:e8:20:34 > f6:ed:db:8e:76:6e, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 19382, offset 0, flags [none], proto ICMP (1), length 84)\r\n 11.200.1.1 > 11.200.2.1: ICMP echo reply, id 4534, seq 46, length 64\r\n```\r\n# 3. geneve口放置到自定义网桥中\r\n将geneve口放到网桥上,路由下一跳指向br0\r\n```shell\r\n# host a with ip 192.168.0.251\r\nbrctl addbr br0\r\nip address add 12.200.1.1/24 dev br0\r\nip link set br0 up\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.250 dstport 6082\r\nip link set geneve1 up\r\nbrctl addif br0 geneve1\r\n\r\n# host a with ip 192.168.0.250\r\nbrctl addbr br0\r\nip address add 12.200.1.2/24 dev br0\r\nip link set br0 up\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.251 dstport 6082\r\nip link set geneve1 up\r\nbrctl addif br0 geneve1\r\n```\r\n抓包如下\r\n![[6082.pcap]]\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250102231759112.png)\r\nbr0口始发,通过geneve隧道发起arp request。arp报文是通过geneve1口学习到的,所以br0会把发往对端的流量送到geneve1口。\r\n> 注意:主机和br0网桥各有一张mac表\r\n```shell\r\nroot@iZbp1b28f6ecl05u00dl9lZ:~# arp -n\r\nAddress HWtype HWaddress Flags Mask Iface\r\n169.254.67.1 (incomplete) veth-host\r\n169.254.67.1 ether ce:97:8e:75:90:df C br0\r\n10.0.0.253 ether ee:ff:ff:ff:ff:ff C eth0\r\n12.200.1.2 ether 8a:8f:4c:0a:71:c1 C br0\r\n10.0.0.250 ether ee:ff:ff:ff:ff:ff C eth0\r\nroot@iZbp1b28f6ecl05u00dl9lZ:~# brctl showmacs br0\r\nport no\tmac addr\t\tis local?\tageing timer\r\n 2\t76:7c:8c:b6:9e:01\tyes\t\t 0.00\r\n 2\t76:7c:8c:b6:9e:01\tyes\t\t 0.00\r\n 1\t8a:8f:4c:0a:71:c1\tno\t\t 3.02\r\n 1\tba:04:f4:74:9d:8f\tyes\t\t 0.00\r\n 1\tba:04:f4:74:9d:8f\tyes\t\t 0.00\r\nroot@iZbp1b28f6ecl05u00dl9lZ:~# brctl show br0\r\nbridge name\tbridge id\t\tSTP enabled\tinterfaces\r\nbr0\t\t8000.6e25202550d3\tno\t\t geneve1\r\n\t\t\t\t\t\t\t veth-host\r\n```\r\n# 4. 容器使用geneve隧道\r\n## 4.1. 将geneve隧道直接移到容器命名空间\r\nhost a\r\n```shell\r\ndocker run -itd --privileged --network=none --name client1 myubuntu bash\r\n# 查找容器的进程ID \r\nPID=$(docker inspect -f '' client1)\r\nsudo mkdir -p /var/run/netns\r\nsudo ln -s /proc/$PID/ns/net /var/run/netns/$PID\r\n\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.250 dstport 6082\r\nip link set geneve1 netns $PID\r\nip netns exec $PID ip link set geneve1 up\r\nip netns exec $PID ip addr add 169.254.67.1/32 dev geneve1\r\nip netns exec $PID ip route add default dev geneve1\r\n```\r\nhost b\r\n```shell\r\nbrctl addbr br0\r\nip address add 12.200.1.2/24 dev br0\r\nip link set br0 up\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.251 dstport 6082\r\nip link set geneve1 up\r\nbrctl addif br0 geneve1\r\nip route add 169.254.67.1/32 dev br0\r\n```\r\n### 4.1.1. 抓包分析\r\n在host a容器中执行`ping 12.200.1.2`\r\n\r\nhost b的br0网桥抓包\r\n```shell\r\ntcpdump: listening on br0, link-type EN10MB (Ethernet), capture size 262144 bytes\r\n17:31:59.955468 ae:3d:e0:0c:58:73 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Ethernet (len 6), IPv4 (len 4), Request who-has 12.200.1.2 tell 169.254.67.1, length 28\r\n17:31:59.955508 5a:d5:32:45:37:9e > ae:3d:e0:0c:58:73, ethertype ARP (0x0806), length 42: Ethernet (len 6), IPv4 (len 4), Reply 12.200.1.2 is-at 5a:d5:32:45:37:9e, length 28\r\n17:31:59.956375 ae:3d:e0:0c:58:73 > 5a:d5:32:45:37:9e, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 7972, offset 0, flags [DF], proto ICMP (1), length 84)\r\n 169.254.67.1 > 12.200.1.2: ICMP echo request, id 83, seq 1, length 64\r\n17:31:59.956419 5a:d5:32:45:37:9e > ae:3d:e0:0c:58:73, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 11877, offset 0, flags [none], proto ICMP (1), length 84)\r\n 12.200.1.2 > 169.254.67.1: ICMP echo reply, id 83, seq 1, length 64\r\n```\r\nhost b的geneve1口抓包\r\n```shell\r\ntcpdump: listening on geneve1, link-type EN10MB (Ethernet), capture size 262144 bytes\r\n17:31:59.955468 ae:3d:e0:0c:58:73 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Ethernet (len 6), IPv4 (len 4), Request who-has 12.200.1.2 tell 169.254.67.1, length 28\r\n17:31:59.955512 5a:d5:32:45:37:9e > ae:3d:e0:0c:58:73, ethertype ARP (0x0806), length 42: Ethernet (len 6), IPv4 (len 4), Reply 12.200.1.2 is-at 5a:d5:32:45:37:9e, length 28\r\n17:31:59.956375 ae:3d:e0:0c:58:73 > 5a:d5:32:45:37:9e, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 7972, offset 0, flags [DF], proto ICMP (1), length 84)\r\n 169.254.67.1 > 12.200.1.2: ICMP echo request, id 83, seq 1, length 64\r\n17:31:59.956422 5a:d5:32:45:37:9e > ae:3d:e0:0c:58:73, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 11877, offset 0, flags [none], proto ICMP (1), length 84)\r\n 12.200.1.2 > 169.254.67.1: ICMP echo reply, id 83, seq 1, length 64\r\n```\r\nhost b上接口信息\r\n```shell\r\n15: br0: mtu 1500 qdisc noqueue state UP group default qlen 1000\r\n link/ether 5a:d5:32:45:37:9e brd ff:ff:ff:ff:ff:ff\r\n inet 12.200.1.2/24 scope global br0\r\n valid_lft forever preferred_lft forever\r\n inet6 fe80::504c:edff:fe1f:9a5d/64 scope link \r\n valid_lft forever preferred_lft forever\r\n16: geneve1: mtu 1500 qdisc noqueue master br0 state UNKNOWN group default qlen 1000\r\n link/ether 5a:d5:32:45:37:9e brd ff:ff:ff:ff:ff:ff\r\n inet 12.200.1.3/24 scope global geneve1\r\n valid_lft forever preferred_lft forever\r\n inet6 fe80::58d5:32ff:fe45:379e/64 scope link \r\n valid_lft forever preferred_lft forever\r\n```\r\nhost a上容器中的arp表\r\n```shell\r\n~# ip netns exec $PID arp -n \r\nAddress HWtype HWaddress Flags Mask Iface\r\n12.200.1.2 ether 5a:d5:32:45:37:9e C geneve1\r\n```\r\nhost b上arp表以及br0网桥的mac表\r\n```shell\r\n~# arp -n\r\nAddress HWtype HWaddress Flags Mask Iface\r\n169.254.67.1 ether ae:3d:e0:0c:58:73 C br0\r\n192.168.1.1 ether fa:16:3e:17:96:ac C eth0\r\n192.168.1.53 ether fa:16:3e:ac:87:86 C eth0\r\n192.168.1.254 ether fa:fa:fa:fa:fa:01 C eth0\r\n~# brctl showmacs br0\r\nport no\tmac addr\t\tis local?\tageing timer\r\n 1\t5a:d5:32:45:37:9e\tyes\t\t 0.00\r\n 1\t5a:d5:32:45:37:9e\tyes\t\t 0.00\r\n 1\tae:3d:e0:0c:58:73\tno\t\t 0.25\r\n~# brctl show br0 \r\nbridge name\tbridge id\t\tSTP enabled\tinterfaces\r\nbr0\t\t8000.5ad53245379e\tno\t\tgeneve1 \r\n# geneve1代表网桥上的Port no为1的口,5a:d5:32:45:37:9e就是geneve1口的mac,loca:yes代表直连;ae:3d:e0:0c:58:73是host a中容器的源mac,local:no代表学习到的\r\n```\r\n\r\n1. host a:容器发起arp request广播消息\r\n2. host a: 主机a的geneve1口为隧道口,将广播消息封装geneve隧道,匹配主机路由从主机a的eth口发出\r\n3. host b: 主机b的eth口收到geneve报文,解封装外层隧道信息,从主机b的geneve1口发出广播\r\n4. host b: 流量到达host b的br0网桥,网桥记录`ae:3d:e0:0c:58:73`对应端口为geneve1\r\n5. host b: 网桥接口收到广播报文,发现目的IP是自己**且请求IP的网关**也是自己,于是便单播响应arp reply;\r\n6. host b: 流量到达br0网桥,发现回程`ae:3d:e0:0c:58:73`对应的端口为geneve1,于是从geneve1口发出\r\n7. host b: 封装geneve隧道,从主机b的eth口发出\r\n8. host a: 主机a的eth口收到geneve报文,解封装外层隧道信息,送到主机a容器中的geneve1口,容器完成arp学习\r\n9. host a: 容器icmp request单播消息,匹配默认路由下一跳送geneve1口\r\n10. host a: 主机a的geneve1口为隧道口,收到广播消息后封装geneve隧道,从主机a的eth口发出\r\n11. host b: 主机b的eth口收到geneve报文,解封装外层隧道信息,从geneve1口发出到达br0网桥\r\n13. 流量到达br0网桥,查找mac表,发现IP是网桥接口,于是br0网桥接口响应ICMP reply,然后发出回到br0网桥\r\n14. 网桥查找mac转发表,出接口是`ae:3d:e0:0c:58:73`,于是从geneve1口发出\r\n15. host b: 流量封装geneve隧道,从主机b的eth口发出\r\n16. host a: 主机a的eth口收到geneve报文,解封装外层隧道信息,发现目的是自己,通信结束\r\n\r\n> 我们在主机b上添加了一条目的地址是主机容器地址,下一跳送br0网桥接口的路由。如果这条路由下一跳指向主机a的geneve1口会怎么样?arp请求到达主机b的geneve1口的时候,会发现网关是自己,自己应该代答,但是请求的IP却不是自己,这种情况下就会丢包了(网关不会代答二层,二层内的真实主机应该回答) [[ARP#1. arp响应的条件]]]\r\n## 4.2. 容器通过veth连接宿主机网桥上geneve口\r\nhost a配置\r\n```shell\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.250 dstport 6082\r\nip link set geneve1 up\r\nbrctl addbr br0\r\nip link set br0 up\r\nbrctl addif br0 geneve1\r\n\r\ndocker run -itd --privileged --network=none --name client1 myubuntu bash\r\nPID=$(docker inspect -f '' client1)\r\nsudo mkdir -p /var/run/netns\r\nsudo ln -s /proc/$PID/ns/net /var/run/netns/$PID\r\nip link add veth-host type veth peer name veth-client1\r\nip link set veth-host up\r\nbrctl addif br0 veth-host\r\nip link set veth-client1 netns $PID\r\nip netns exec $PID ip addr add 169.254.67.1/16 dev veth-client1 \r\nip netns exec $PID ip link set veth-client1 up \r\nip netns exec $PID ip route add default dev veth-client1\r\n```\r\nhost b配置\r\n```shell\r\nbrctl addbr br0\r\nip address add 12.200.1.2/24 dev br0\r\nip link set br0 up\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.251 dstport 6082\r\nip link set geneve1 up\r\nbrctl addif br0 geneve1\r\nroute add 169.254.67.1/32 dev br0\r\n```\r\niptables规则\r\n```shell\r\n# iptables -nvL FORWARD \r\nChain FORWARD (policy DROP 1246 packets, 105K bytes)\r\n pkts bytes target prot opt in out source destination \r\n 1246 105K DOCKER-ISOLATION all -- * * 0.0.0.0/0 0.0.0.0/0 \r\n 0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0 \r\n 0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED\r\n 0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0 \r\n 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0\r\n```\r\n由于经过网桥的目的ip不是本机地址,因此会被[[bridge|iptables]]的forward链过滤,这里主机a上还需要执行以下命令:\r\n```shell\r\niptables -I FORWARD -i br0 -o br0 -j ACCEPT\r\n# 或者直接关闭forward\r\nsysctl -w net.bridge.bridge-nf-call-iptables=0\r\n```\r\n为了避免arp学习,可以通过添加静态mac解决\r\n```shell\r\n# 配置一个假网关,给假网关配置静态mac,流量到达网桥由于网桥不知道mac对应port,因此会在所有非发送端口上泛洪,也包括geneve1口,但是如果想要对端回包,则这个mac必须和目的端mac匹配,否则对端会丢包,或者对端设备支持不校验目的mac\r\nip netns exec $PID ip route add default via 169.254.175.253 dev veth-client1\r\nip netns exec $PID arp -s 169.254.175.253 52:7d:b1:92:5a:0f\r\n```\r\n## 4.3. 同一宿主机上不同容器使用相同VNI不同DstPORT\r\nhost a配置两个容器的源IP一样,使用不同目的port区分隧道\r\n```shell\r\ndocker run -itd --privileged --network=none --name client1 myubuntu bash\r\n# 查找容器的进程ID \r\nPID=$(docker inspect -f '' client1)\r\nsudo mkdir -p /var/run/netns\r\nsudo ln -s /proc/$PID/ns/net /var/run/netns/$PID\r\n\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.250 dstport 6081\r\n\tip link set geneve1 netns $PID\r\nip netns exec $PID ip link set geneve1 up\r\nip netns exec $PID ip addr add 169.254.175.252/32 dev geneve1\r\nip netns exec $PID ip route add default dev geneve1\r\n\r\n\r\ndocker run -itd --privileged --network=none --name client2 myubuntu bash\r\n# 查找容器的进程ID \r\nPID=$(docker inspect -f '' client2)\r\nsudo mkdir -p /var/run/netns\r\nsudo ln -s /proc/$PID/ns/net /var/run/netns/$PID\r\n\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.250 dstport 6082\r\nip link set geneve1 netns $PID\r\nip netns exec $PID ip link set geneve1 up\r\nip netns exec $PID ip addr add 169.254.176.252/32 dev geneve1\r\nip netns exec $PID ip route add default dev geneve1\r\n```\r\nhost b相同配置\r\n```shell\r\ndocker run -itd --privileged --network=none --name client1 myubuntu bash\r\n# 查找容器的进程ID \r\nPID=$(docker inspect -f '' client1)\r\nsudo mkdir -p /var/run/netns\r\nsudo ln -s /proc/$PID/ns/net /var/run/netns/$PID\r\n\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.251 dstport 6081\r\nip link set geneve1 netns $PID\r\nip netns exec $PID ip link set geneve1 up\r\nip netns exec $PID ip addr add 169.254.177.252/32 dev geneve1\r\nip netns exec $PID ip route add default dev geneve1\r\n\r\n\r\ndocker run -itd --privileged --network=none --name client2 myubuntu bash\r\n# 查找容器的进程ID \r\nPID=$(docker inspect -f '' client2)\r\nsudo mkdir -p /var/run/netns\r\nsudo ln -s /proc/$PID/ns/net /var/run/netns/$PID\r\n\r\nip link add name geneve1 type geneve vni 1000 remote 10.0.0.251 dstport 6082\r\nip link set geneve1 netns $PID\r\nip netns exec $PID ip link set geneve1 up\r\nip netns exec $PID ip addr add 169.254.178.252/32 dev geneve1\r\nip netns exec $PID ip route add default dev geneve1\r\n```\r\n此时两个主机上的两个容器可以通过两个仅目的端口不一样的隧道同时互通\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250106233311014.png)\r\n### 4.3.1. Extension\r\n为了避免arp学习,可以在容器内配置静态arp,以一个容器举例\r\n```shell\r\n# 修改geneve1口网段为31位,配置网关为169.254.175.253,这样所有流量都会从geneve1口出,同时可以添加静态arp,避免arp学习不到网关mac\r\nip addr add 169.254.175.252/31 dev geneve1\r\nip route add default via 169.254.175.253 dev geneve1\r\narp -s 169.254.175.253 1e:53:8a:c3:1a:be\r\n```\r\n> 这里静态mac地址是添加的对端容器169.254.177.252的真实mac,否则对端会由于mac不对而直接丢包。这种静态配置的方式只适合通信对端只有一个地址且mac固定的情况。否则需要对端对目的mac不校验。\r\n# 5. 注意点\r\n1. 要配置好回程路由,否则会认为回程不可达,不会发送arp响应"},{"id":"network namespace基础","title":"Network Namespace基础","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"network-namespace基础","description":"可以创建一个完全隔离的新网络环境,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables等等。总之,与网络有关的组件都是独立的。 可以看到我们刚才创建的网络环境 进入虚拟网络环境,使用命令 只能看到lo口 这样我们可以在新的网络环境中打开一个shell。 连...","relativePath":"Tech/Os/Linux/Network/network namespace基础.md","rawContent":"```shell\r\nip netns add net0\r\n```\r\n可以创建**一个完全隔离的新网络环境**,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables等等。总之,与网络有关的组件都是独立的。\r\n\r\n```shell\r\nip netns list\r\n``` \r\n可以看到我们刚才创建的网络环境\r\n\r\n进入虚拟网络环境,使用命令\r\n```shell\r\nip netns exec net0 [command]\r\n```\r\n只能看到lo口\r\n```text\r\n[10.1.0.63_ ~]#ip netns exec net0 ip a\r\n1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000\r\n link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\r\n[10.1.0.63_ ~]#ip a\r\n1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\r\n link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\r\n inet 127.0.0.1/8 scope host lo\r\n valid_lft forever preferred_lft forever\r\n inet6 ::1/128 scope host \r\n valid_lft forever preferred_lft forever\r\n2: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000\r\n link/ether fa:16:3e:c9:c8:07 brd ff:ff:ff:ff:ff:ff\r\n inet 10.1.0.63/24 brd 10.1.0.255 scope global noprefixroute dynamic eth0\r\n valid_lft 104615901sec preferred_lft 104615901sec\r\n inet6 fe80::38b7:34f8:3bde:5f99/64 scope link noprefixroute \r\n valid_lft forever preferred_lft forever\r\n```\r\n\r\n`ip netns exec net0 bash`这样我们可以在新的网络环境中打开一个shell。\r\n\r\n连接两个网络环境\r\n\r\n新的网络环境里面没有任何网络设备,并且也无法和外部通讯,就是一个孤岛,通过下面介绍的这个方法可以把两个网络环境连起来,简单的说,就是在两个网络环境之间拉一根网线\r\n\r\n`ip netns add net1`,先创建另一个网络环境net1,我们的目标是把net0与net1连起来`ip link add type veth`\r\n\r\n```shell\r\n[10.1.0.63_ ~]#ip a\r\n1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\r\n link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\r\n inet 127.0.0.1/8 scope host lo\r\n valid_lft forever preferred_lft forever\r\n inet6 ::1/128 scope host \r\n valid_lft forever preferred_lft forever\r\n2: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000\r\n link/ether fa:16:3e:c9:c8:07 brd ff:ff:ff:ff:ff:ff\r\n inet 10.1.0.63/24 brd 10.1.0.255 scope global noprefixroute dynamic eth0\r\n valid_lft 104615556sec preferred_lft 104615556sec\r\n inet6 fe80::38b7:34f8:3bde:5f99/64 scope link noprefixroute \r\n valid_lft forever preferred_lft forever\r\n3: veth0@veth1: mtu 1500 qdisc noop state DOWN group default qlen 1000\r\n link/ether 3a:42:ef:dc:66:05 brd ff:ff:ff:ff:ff:ff\r\n4: veth1@veth0: mtu 1500 qdisc noop state DOWN group default qlen 1000\r\n link/ether f6:25:ff:bf:8f:26 brd ff:ff:ff:ff:ff:ff\r\n```\r\n\r\n这里创建连一对veth虚拟网卡,类似pipe,发给veth0的数据包veth1那边会收到,发给veth1的数据包veth0会收到。就相当于给机器安装了两个网卡,并且之间用网线连接起来了\r\n\r\n设置veth的两端分别连接net0和net1\r\n```shell\r\nip link set veth0 netns net0\r\nip link set veth1 netns net1\r\n```\r\n\r\n这两条命令的意思就是把veth0移动到net0环境里面,把veth1移动到net1环境里面,我们看看结果\r\n```shell\r\n[10.1.0.63_ ~]#ip a\r\n1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\r\n link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\r\n inet 127.0.0.1/8 scope host lo\r\n valid_lft forever preferred_lft forever\r\n inet6 ::1/128 scope host \r\n valid_lft forever preferred_lft forever\r\n2: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000\r\n link/ether fa:16:3e:c9:c8:07 brd ff:ff:ff:ff:ff:ff\r\n inet 10.1.0.63/24 brd 10.1.0.255 scope global noprefixroute dynamic eth0\r\n valid_lft 104615332sec preferred_lft 104615332sec\r\n inet6 fe80::38b7:34f8:3bde:5f99/64 scope link noprefixroute \r\n valid_lft forever preferred_lft forever\r\n```\r\nveth0 veth1已经在我们的环境里面消失了,并且分别出现在net0与net1里面。\r\n```shell\r\n[10.1.0.63_ ~]#ip netns exec net0 ip a\r\n1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000\r\n link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\r\n3: veth0@if4: mtu 1500 qdisc noop state DOWN group default qlen 1000\r\n link/ether 3a:42:ef:dc:66:05 brd ff:ff:ff:ff:ff:ff link-netnsid 1\r\n[10.1.0.63_ ~]#ip netns exec net1 ip a\r\n1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000\r\n link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\r\n4: veth1@if3: mtu 1500 qdisc noop state DOWN group default qlen 1000\r\n link/ether f6:25:ff:bf:8f:26 brd ff:ff:ff:ff:ff:ff link-netnsid 0\r\n```\r\n\r\n下面我们简单测试一下net0与net1的联通性\r\n```shell\r\nip netns exec net0 ip link set veth0 up\r\nip netns exec net0 ip address add 10.0.1.1/24 dev veth0\r\nip netns exec net1 ip link set veth1 up\r\nip netns exec net1 ip address add 10.0.1.2/24 dev veth1\r\n```\r\n\r\n分别配置好两个设备,然后用ping测试一下联通性:\r\n```text\r\n[10.1.0.63_ ~]#ip netns exec net0 ping -c 3 10.0.1.2\r\nPING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.\r\n64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.024 ms\r\n64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.020 ms\r\n64 bytes from 10.0.1.2: icmp_seq=3 ttl=64 time=0.021 ms\r\n\r\n--- 10.0.1.2 ping statistics ---\r\n3 packets transmitted, 3 received, 0% packet loss, time 1999ms\r\nrtt min/avg/max/mdev = 0.020/0.021/0.024/0.005 ms\r\n```\r\n\r\n**一个稍微复杂的网络环境**\r\n\r\n\r\n\r\n创建虚拟网络环境并且连接网线\r\nip netns add net0
ip netns add net1
ip netns add bridge
ip link add **type** veth
ip link **set** dev veth0 name net0-bridge netns net0
ip link **set** dev veth1 name bridge-net0 netns bridge
ip link add **type** veth
ip link **set** dev veth0 name net1-bridge netns net1
ip link **set** dev veth1 name bridge-net1 netns bridge\r\n\r\n在bridge中创建并且设置br设备\r\nip netns **exec** bridge brctl addbr br
ip netns **exec** bridge ip link **set** dev br up
ip netns **exec** bridge ip link **set** dev bridge-net0 up
ip netns **exec** bridge ip link **set** dev bridge-net1 up
ip netns **exec** bridge brctl addif br bridge-net0\r\nip netns **exec** bridge brctl addif br bridge-net1
\r\n先在bridge的命名空间新建一个网桥,名字叫br,然后在bridge的空间新建两个dev:**bridge-net0**和**bridge-net1**,然后将两个dev添加到网桥br中。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250114221924595.png)\r\n\r\n\r\n然后配置两个虚拟环境的网卡\r\nip netns **exec** net0 ip link **set** dev net0-bridge up
ip netns **exec** net0 ip address add 10.0.1.1/24 dev net0-bridge\r\nip netns **exec** net1 ip link **set** dev net1-bridge up
ip netns **exec** net1 ip address add 10.0.1.2/24 dev net1-bridge|\r\n\r\n测试\r\nip netns **exec** net0 ping -c 3 10.0.1.2PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_req=1 ttl=64 time=0.121 ms64 bytes from 10.0.1.2: icmp_req=2 ttl=64 time=0.072 ms64 bytes from 10.0.1.2: icmp_req=3 ttl=64 time=0.069 ms
--- 10.0.1.2 ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.069/0.087/0.121/0.025 ms|"},{"id":"route table与VRF","title":"Route Table与VRF","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"route-table与vrf","description":"在 Linux 系统中,支持使用多个 route table(路由表) 是为了增强网络配置的灵活性和功能性。多路由表的主要作用如下: 1. 实现策略路由(Policy Routing) 默认情况下,Linux 使用主路由表()中的路由规则来决定流量的转发路径。然而,某些场景下可能需要基于流量的源地址...","relativePath":"Tech/Os/Linux/Network/route table与VRF.md","rawContent":"在 Linux 系统中,支持使用多个 **route table(路由表)** 是为了增强网络配置的灵活性和功能性。多路由表的主要作用如下:\r\n# 1. 实现策略路由(Policy Routing)\r\n默认情况下,Linux 使用主路由表(`main`)中的路由规则来决定流量的转发路径。然而,某些场景下可能需要基于流量的**源地址**、**目标地址**、**入接口** 等条件来选择不同的路由表。这就是策略路由的核心功能。\r\n- **示例场景:**\r\n - 流量从不同的网络接口(如 `eth0` 和 `eth1`)进来时,需使用不同的网关。\r\n - 根据源 IP 地址选择不同的路由路径。\r\n **配置示例:**\r\n```shell\r\n# 定义一个新的路由表 \r\necho \"200 custom_table\" >> /etc/iproute2/rt_tables \r\n# 向该路由表添加路由 \r\nip route add 192.168.1.0/24 dev eth1 table custom_table \r\n# 定义策略:源地址 192.168.1.100 的流量使用 custom_table \r\nip rule add from 192.168.1.100 table custom_table`\r\n```\r\n# 2. 实现多网关配置\r\n多路由表常用于多网关场景下的流量分配。例如,当一台服务器有多个网卡连接到不同的 ISP,且希望不同网卡使用各自的网关时,可以借助多路由表实现。\r\n- **示例场景:**\r\n - 一个网卡连接到 ISP1,另一个网卡连接到 ISP2。\r\n - 内部流量从 ISP1 出,外部流量从 ISP2 出。\r\n **配置示例:**\r\n```shell\r\n# 为 ISP1 创建路由表 \r\necho \"201 isp1_table\" >> /etc/iproute2/rt_tables \r\nip route add default via 192.168.1.1 dev eth0 table isp1_table \r\n# 为 ISP2 创建路由表 \r\necho \"202 isp2_table\" >> /etc/iproute2/rt_tables \r\nip route add default via 10.0.0.1 dev eth1 table isp2_table \r\n# 定义规则:源地址 192.168.1.0/24 使用 ISP1 \r\nip rule add from 192.168.1.0/24 table isp1_table \r\n# 定义规则:源地址 10.0.0.0/24 使用 ISP2 \r\nip rule add from 10.0.0.0/24 table isp2_table\r\n```\r\n# 3. 解决路由冲突和复杂网络需求\r\n\r\n在某些复杂网络环境中,不同的子网可能需要不同的路由规则。如果使用单一的主路由表,配置会非常复杂甚至不可行。使用多路由表可以将不同的路由需求分开管理,降低复杂性。\r\n- **示例场景:**\r\n - 公司网络中,VPN 流量、内网流量、公网流量需要分开走不同的路由。\r\n## 3.1. 支持高可用和冗余(Failover)\r\n通过多个路由表,可以为不同的网络接口设置备用路由路径。当某个接口失效时,策略路由可以自动切换到备用路由表,以实现高可用性。\r\n- **示例场景:**\r\n - 两条物理线路,一条为主线路,另一条为备用线路。\r\n - 使用 `ip rule` 和监控脚本实现线路的自动切换。\r\n## 3.2. 实现流量隔离**\r\n\r\n多路由表可以帮助隔离流量。例如,在多租户系统中,每个租户可能有自己的网络配置,流量需要单独路由而不影响其他租户。\r\n\r\n- **示例场景:**\r\n - 每个租户有独立的网段和网关,需通过不同的路由表管理其流量。\r\n\r\n## 3.3. 支持容器和虚拟机的多网络配置**\r\n对于运行容器或虚拟机的主机,可能需要根据虚拟机或容器的网络配置选择不同的路由路径。多路由表可以用来管理不同的网络环境。\r\n- **示例场景:**\r\n - Docker 容器通过 `docker0` 使用默认路由,KVM 虚拟机通过 `br0` 使用另一条路由。\r\n# 4. **总结**\r\n多路由表的主要作用是提供灵活的网络配置能力,适用于以下场景:\r\n1. 策略路由(基于条件选择路由表)。\r\n2. 多网关配置(多出口流量管理)。\r\n3. 解决复杂的网络需求和路由冲突。\r\n4. 高可用和冗余网络。\r\n5. 流量隔离(多租户或应用隔离)。\r\n6. 容器或虚拟化场景下的多网络支持。\r\n\r\n# 5. VRF\r\n在现代网络环境中,使用 VRF(Virtual Routing and Forwarding)确实是更高效和优雅的解决方案。相比通过多路由表和策略路由手动实现复杂的网络需求,VRF 提供了一个更结构化、模块化的方式来隔离和管理流量。以下是关于为什么可以考虑使用 VRF,以及其与传统多路由表方式的比较。\r\n## 5.1. **VRF 与传统多路由表的对比**\r\n\r\n| 特性 | 多路由表 | VRF |\r\n| ------- | ------------------------ | -------------------- |\r\n| **隔离性** | 手动配置,通过策略路由(`ip rule`)实现 | 天然隔离,每个 VRF 拥有独立的路由表 |\r\n| **易用性** | 复杂,需要维护路由表、策略规则和 NAT | 简单,直接将接口加入 VRF |\r\n| **扩展性** | 随规模增长复杂度上升 | 面向多租户和大规模网络设计,扩展性强 |\r\n| **兼容性** | 标准 Linux 工具,易于集成 | 需要 Linux 内核支持 VRF |\r\n| **功能性** | 功能单一,仅用于流量分离和路由策略 | 支持更丰富的功能,例如 VRF 间通信 |\r\n"},{"id":"tcp关键内核参数","title":"Tcp关键内核参数","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":11,"slug":"tcp关键内核参数","description":"1. tcpsynretries 这个参数值设置的是client发送SYN如果server端不回复的话,ff0000\">重传SYN的次数。对我们的直接影响就是connet建立连接时的超时时间。当然Java通过一些C原生系统调用的组合使得我们可以进行超时时间的设置。在Linux里面默认设置是5,下面给...","relativePath":"Tech/Os/Linux/Network/tcp关键内核参数.md","rawContent":"# 1. tcp_syn_retries\r\n这个参数值设置的是client发送SYN如果server端不回复的话,重传SYN的次数。对我们的直接影响就是connet建立连接时的超时时间。当然Java通过一些C原生系统调用的组合使得我们可以进行超时时间的设置。在Linux里面默认设置是5,下面给出建议值3和默认值5之间的超时时间。\r\n\r\n|tcp_syn_retries|timeout|\r\n|---|---|\r\n|1|min(so_sndtimeo,3s)|\r\n|2|min(so_sndtimeo,7s)|\r\n|3|min(so_sndtimeo,15s)|\r\n|4|min(so_sndtimeo,31s)|\r\n|5|min(so_sndtimeo,63s)|\r\n![[Pasted image 20250323171012.png]]\r\n```bash\r\nsysctl net.ipv4.tcp_syn_retries\r\nnet.ipv4.tcp_syn_retries = 1\r\n```\r\n\r\n>[!note]\r\n>如果我们在应用中忘记了设置[[数据库关键配置#2. connectTimeout|connectTimeout]],同时tcp_syn_retries如果设置为5,就会需要1分钟才会超时断连。\r\n# 2. trp_retries2\r\n![[Tcp重传#1.4. 重传机制]]\r\n# 3. tcp_tw_reuse和tcp_tw_recycle\r\n## 3.1. tcp timewait\r\nTIME_WAIT 是主动断开连接的一方会出现的,客户端,服务器都有可能出现。当客户端主动断开连接时,发出最后一个ACK后就会处于 TIME_WAIT状态。当服务器主动断开连接时,发出最后一个ACK后就会处于 TIME_WAIT状态\r\n\r\n目的:\r\n1. 为了可靠地关闭TCP连接,能够在对端没有收到ACK,从而发生FIN重传情况下有机会重新发送ACK\r\n2.  防止上一次连接中的包,迷路后重新出现,影响新连接\r\n\r\nTIME_WAIT状态持续2MSL时间,MSL就是maximum segment lifetime(最大报文段的生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失(被丢弃)。RFC 793中规定MSL为2分钟,实际应用中,可能为30S,1分钟,2分钟。\r\n\r\nubuntu系统,输入如下命令后可以看到,时间为60秒\r\n```shell\r\nroot@vultr:~# sysctl net.ipv4.tcp_fin_timeout\r\nnet.ipv4.tcp_fin_timeout = 30\r\n```\r\n## 3.2. timestamp\r\nPAWS(Protection Against Wrapped Sequences,防绕回序列号)功能基于TCP的Timestamps选项实现,用于拒绝接收到的过期的重复报文。PAWS假设每个报文都携带有TSopt选项数据,其中的时间戳TSval保持单调递增,所有,当接收到一个报文其TSval值小于之前在此连接中接收到的时间戳(ts_recent),即认定此报文为过期报文,将其丢弃。\r\n\r\n时间戳(Timestamp)是基于IP粒度的,这意味着如果存在一个时间戳比较晚的连接,那么所有比这个时间戳早的tcp连接都无法成功建立。\r\n\r\n默认情况下内核是开启timestamps选项的。可通过PROC文件tcp_timestamps控制选项行为:\r\n- 值为0时,表示关闭timestamps选项\r\n- 值为1时,表示使能RFC1323(最新-RFC7323)中定义的timestamps选项,并且使用随机偏移值与timesstamp叠加,以抵御攻击\r\n- 值为2时,仅开启timestamps选项,不使用偏移值。\r\n## 3.3. tcp_tw_recycle\r\n开启 (`1`) 该参数后,系统会加快回收 TIME-WAIT状态的TCP连接,减少 TIME-WAIT 连接占用的资源(如端口和内存)。 \r\n```c\r\n=====linux-2.6.37 net/ipv4/tcp.c 126================\r\n#define TCP_RTO_MAX ((unsigned)(120*HZ))\r\n#define TCP_RTO_MIN ((unsigned)(HZ/5))\r\n==========================================\r\n```\r\n这里的HZ是1s,因此可以得出RTO最大是120s,最小是200ms,对于局域网的机器来说,正常情况下RTO基本上就是200ms,因[3.5 RTO就是700ms](https://zhuanlan.zhihu.com/p/567088021)。也就是说,快速回收是TIME_WAIT的状态持续700ms,而不是正常的2MSL。因此我们很难在开启后看到TIME_WAIT状态,我们不停通过命令的查看TIME_WAIT状态的连接,偶尔才能看到1个。\r\n### 3.3.1. 基于时间戳 (TCP Timestamp) 机制\r\n`tcp_tw_recycle` 依赖 TCP 时间戳选项(`tcp_timestamps` 需要开启)。它通过检查 TCP 时间戳来判断是否可以安全地回收 TIME-WAIT 连接。\r\n\r\n如果TIME_WAIT期间,其远程主机向当前主机发起tcp连接,则其timestamp必须存在且严格递增,否则丢弃请求。 对于TIME_WAIT结束后的连接请求,则不再校验其timestamp。\r\n### 3.3.2. 存在的问题\r\n如果`tcp_tw_recycle=1`,对同一个IP的连接会存在限制,后建立的连接的时间戳必须要**大于**之前建立连接的最后时间戳。如果同时满足了以下条件,就会导致后面的连接无法成功建立。\r\n- 不同的机器使用同一个NAT的IP\r\n- 时间不同步,且后建立连接的的主机时间落后\r\n- 前面的连接还处于`timewait`状态\r\n\r\n下图就是一个常见问题\r\n- 内网 `nginx1 (10.1.1.10)` 和 `nginx2 (10.1.1.20)` 复用防火墙(NAT)同一个`IP(20.1.1.1)`访问外部 `appserver (30.1.1.1)`。\r\n- `nginx1` 连接成功后,`外部 appserver` 记录 `nginx1` 的时间戳。\r\n- `nginx2` 再次发起连接时,如果它的时间戳比 `nginx1` 小,`外部 appserver` 直接丢弃请求。`nginx2`基本永远没有机会连接到`appserver`。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321215215207.png)\r\n原理如下:\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321213244504.png)\r\n> [!warning]\r\n由于这个参数导致的问题,高版本内核已经去掉了这个参数,也强烈建议不要打开这个参数。Linux 内核从**[4.12 版本](https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4396e46187ca5070219b81773c4e65088dac50cc)**开始**移除了 `tcp_tw_recycle`**\r\n## 3.4. tcp_tw_reuse\r\n如果开启该选项的话,客户端在调用connect()函数时,**内核会随机找一个TIME_WAIT状态超过1秒的连接给新的连接复用**。这其实就是相当于缩短了TIME_WAIT状态的持续时间。\r\n> [!note]\r\n`tcp_tw_reuse`主要针对客户端,在客户端主动关闭连接的情况下,可以快速复用time_wait状态连接。这种情况下,服务端属于被动关闭,在收到最后一个ack的时候就已经关闭了连接了。同时,客户端复用连接,能够保证不出现时间倒退的情况,因此是相对安全的。\r\n\r\n在不同的 Linux 内核版本中,`tcp_tw_reuse`的默认值有所不同:\r\n- **旧内核版本**:在早期的 Linux 内核里,`tcp_tw_reuse`默认是关闭的,其值为0\r\n- **较新内核版本**:从 Linux 内核 3.7 开始,`tcp_tw_reuse`默认是开启的,值为1\r\n这里既然我们缩短了TIME_WAIT状态的时间,使其不是2MSL,那么因为引入TIME_WAIT解决的问题就再次出现了,所以tcp_tw_reuse也需要解决这两个问题:\r\n1. 防止历史连接中的数据,被后面相同四元组的连接错误的接收\r\n2. 保证被动关闭连接的一方能够正确的关闭\r\n### 3.4.1. 问题一\r\n因为如果在历史连接上建立了新的连接,而网络中此时还有历史连接残留的数据包存活,那么它们有可能在新连接建立之后到达,那么就会导致接收到错误的数据。TIME_WAIT的解决方法就是等待2MSL之后才进入CLOSED,也就是说耗死了网络中残留的数据包,保证新连接建立时历史数据包全都死亡了。\r\n\r\n防回绕序列号算法要求连接双方维护最近一次收到的数据包的时间戳(Recent TSval),每收到一个新数据包都会读取数据包中的时间戳值跟 Recent TSval 值做比较,**如果发现收到的数据包中时间戳不是递增的,则表示该数据包是过期的,就会直接丢弃这个数据包**。历史数据包显然时间戳必然是早于新连接建立后保存的时间戳的,所以就避免了接收旧连接残留数据的问题。\r\n\r\n序列号本来也是递增的,正常来说延迟的数据包重新到达时序号是对不上的,因为这期间有其他新的数据包到达,会改变期待的序列号。而如果在延迟期间序列号兜了一圈又回到了原本的位置,那么延迟的数据包正好就能被正确的读取,这会导致数据错误,而时间戳保存了最近一次收到数据包的时间,所以就避免了这种情况。\r\n> [!note]\r\n序列号会回绕,所以无法根据序列号来判断当前数据包是不是旧的数据包。\r\n\r\n所以显然使用tcp_tw_reuse需要打开 TCP 时间戳选项,即`net.ipv4.tcp_timestamps=1`(默认即为 1)。\r\n\r\n但是其实tcp_tw_reuse还是存在风险:因为PAWS算法不会防止过期的RST,所以如果前面有残留的RST报文,在新连接建立之后到达,那么就会导致新连接被这个历史的RST包中断。如果此时不跳过TIME_WAIT状态,而是停留2MSL时长,那么这个RST报文就不会出现下一个新的连接。\r\n### 3.4.2. 问题二\r\ntcp_tw_reuse并没办法解决,如果第四次挥手的ACK报文丢失了,而此时这个端口已经被另一个连接使用了,那么原服务端重发的FIN就会发送失败,从而接收到RST报文而异常关闭。所以说tcp_tw_reuse还是存在风险的。\r\n# 4. ref\r\n[Linux上TCP的几个内核参数调优](https://www.cnblogs.com/alchemystar/p/13175276.html)\r\nhttps://xiaolincoding.com/network/3_tcp/tcp_tw_reuse_close.html#tcp-tw-reuse-%E6%98%AF%E4%BB%80%E4%B9%88"},{"id":"Tun Tap介绍","title":"Tun Tap介绍","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":9,"slug":"tun-tap介绍","description":"在计算机网络中,TUN(Tunnel)与TAP(Test Access Point)是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。 - TAP等同于一个以太网设备,它操作第二层数据包...","relativePath":"Tech/Os/Linux/Network/Tun Tap介绍.md","rawContent":"在计算机网络中,TUN(Tunnel)与TAP(Test Access Point)是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。\r\n- TAP等同于一个以太网设备,它操作第二层数据包如以太网数据帧。它以完整的以太网帧形式接收和发送数据包。当数据包到达 TAP 设备时,它会将整个以太网帧存储在操作系统的内核缓冲区中,等待应用程序读取和处理。\r\n- TUN模拟了网络层设备,操作第三层数据包比如IP数据封包。TUN 设备接收的是IP数据包,将其存储在内核缓冲区中,供应用程序获取和处理。\r\nLinux Tun/Tap驱动程序为应用程序提供了两种交互方式:虚拟网络接口和字符设备/dev/net/tun。写入字符设备/dev/net/tun的数据会发送到虚拟网络接口中;发送到虚拟网络接口中的数据也会出现在该字符设备上。多个 TUN 或 TAP 设备都会通过 `/dev/net/tun` 这个文件进行模拟,但可以通过创建和配置设备时指定的名称来区分不同的设备,不同设备使用不同的内核缓冲区管理。\r\n# 1. 应用程序如何操作Tun/Tap\r\n应用程序可以通过标准的Socket API向Tun/Tap接口发送IP数据包,就好像对一个真实的网卡进行操作一样。除了应用程序以外,操作系统也会根据TCP/IP协议栈的处理向Tun/Tap接口发送IP数据包或者以太网数据包,例如ARP或者ICMP数据包。Tun/Tap驱动程序会将Tun/Tap接口收到的数据包原样写入到/dev/net/tun字符设备上,处理Tun/Tap数据的应用程序如VPN程序可以从该设备上读取到数据包,以进行相应处理。\r\n\r\n应用程序也可以通过/dev/net/tun字符设备写入数据包,这种情况下该字符设备上写入的数据包会被发送到Tun/Tap虚拟接口上,进入操作系统的TCP/IP协议栈进行相应处理,就像从物理网卡进入操作系统的数据一样。\r\n\r\nTun虚拟设备和物理网卡的区别是Tun虚拟设备是IP层设备,从/dev/net/tun字符设备上读取的是IP数据包,写入的也只能是IP数据包,因此不能进行二层操作,如发送ARP请求和以太网广播。与之相对的是,Tap虚拟设备是以太网设备,处理的是二层以太网数据帧,从/dev/net/tun字符设备上读取的是以太网数据帧,写入的也只能是以太网数据帧。从这点来看,Tap虚拟设备和真实的物理网卡的能力更接近。\r\n\r\n下图描述了Tap/Tun的工作原理:\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411152133181.png)\r\n\r\n```shell\r\nsudo ip tuntap add dev tun0 mode tun # 创建一个名为tun0的TUN设备\r\nsudo ip addr add 192.168.100.1/24 dev tun0 # tun0设备分配一个IP地址和子网掩码\r\nsudo ip link set dev tun0 up # 启用tun0设备\r\n```\r\n\r\n```shell\r\nroot@vultr:~# ip a\r\n1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\r\n link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\r\n inet 127.0.0.1/8 scope host lo\r\n valid_lft forever preferred_lft forever\r\n inet6 ::1/128 scope host noprefixroute\r\n valid_lft forever preferred_lft forever\r\n2: enp1s0: mtu 1500 qdisc fq state UP group default qlen 1000\r\n link/ether 56:00:05:11:d5:5c brd ff:ff:ff:ff:ff:ff\r\n inet 104.207.129.30/23 metric 100 brd 104.207.129.255 scope global dynamic enp1s0\r\n valid_lft 85848sec preferred_lft 85848sec\r\n inet6 2001:19f0:0:4a4d:5401:5ff:fe11:d55c/64 scope global dynamic mngtmpaddr noprefixroute\r\n valid_lft 2591482sec preferred_lft 604282sec\r\n inet6 fe80::5400:5ff:fe11:d54c/64 scope link\r\n valid_lft forever preferred_lft forever\r\n3: tun0: mtu 1500 qdisc fq state DOWN group default qlen 500\r\n link/none\r\n inet 192.168.100.1/24 scope global tun0\r\n valid_lft forever preferred_lft forever\r\n```\r\n\r\n```python\r\nimport socket\r\nimport os \r\n\r\n# 创建 UDP 套接字并绑定\r\nserver_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\r\nserver_address = ('192.168.100.1', 5000)\r\nserver_socket.bind(server_address)\r\n\r\n\r\nwhile True:\r\n data, client_address = server_socket.recvfrom(1024)\r\n print(f\"Received from client: {data.decode('utf-8')}\")\r\n response = \"Hello from server!\"\r\n server_socket.sendto(response.encode('utf-8'), client_address)\r\n```\r\n\r\n```shell\r\nroot@vultr:~# python3 server.py\r\nReceived from client: Hello from client!\r\n```\r\n\r\n```python\r\nimport socket\r\nimport os\r\n\r\n \r\n\r\n# 创建 UDP 套接字\r\nclient_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\r\nserver_address = ('192.168.100.1', 5000)\r\nmessage = \"Hello from client!\"\r\nclient_socket.sendto(message.encode('utf-8'), server_address)\r\ndata, server_address = client_socket.recvfrom(1024)\r\nprint(f\"Received from server: {data.decode('utf-8')}\")\r\n```\r\n\r\n```shell\r\nroot@vultr:~# python3 client.py\r\nReceived from server: Hello from server!\r\n```\r\n\r\n# 2. 使用Tun/Tap创建点对点隧道\r\n\r\n通过应用程序从/dev/net/tun字符设备中读取或者写入数据看上去并没有太大用处,但通过将Tun/Tap结合物理网络设备使用,我们可以创建一个点对点的隧道。如下图所示,左边主机上应用程序发送到Tun虚拟设备上的IP数据包被VPN程序通过字符设备接收,然后再通过一个TCP或者UDP隧道发送到右端的VPN服务器上,VPN服务器将隧道负载中的原始IP数据包写入字符设备,这些IP包就会出现在右侧的Tun虚拟设备上,最后通过操作系统协议栈和socket接口发送到右侧的应用程序上。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411152156249.png)\r\n上图中的隧道也可以采用Tap虚拟设备实现。使用Tap的话,隧道的负载将是以太数据帧而不是IP数据包,而且还会传递ARP等广播数据包。![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411152157626.png)\r\n# 3. 使用Tap隧道桥接两个远程站点\r\n\r\n如下图所示,可以使用tap建立二层隧道将两个远程站点桥接起来,组成一个局域网。对于两边站点中的主机来说,访问对方站点的主机和本地站点的主机的方式没有区别,都处于一个局域网192.168.0.0/24中。\r\n\r\nVPN主机上有两个物理网卡,其中Eth0用于和对方站点的VPN主机进行通信,建立隧道。Eth1在通过网线连接到以太网交换机的同时也被则加入了Linux Bridge,这相当于用一条网线将Linux Bridge上的一个端口(Eth1)连接到了本地站点的以太网交换机上,Eth1上收到的所有数据包都会被发送到Linux Bridge上,Linux Bridge发给Eth1的数据包也会被发送到以太网交换机上。Linux Bridge上还有一个Tap虚拟网卡,用于VPN程序接收从Linux Bridge上收到的数据包。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411152158486.png)\r\n假设192.168.0.5发出了一个对192.168.0.3的ARP请求,该ARP请求在网络中经过的路径如下:\r\n\r\n1. 192.168.0.5发出ARP请求,询问192.168.0.3的MAC地址。\r\n2. 该ARP请求将被发送到以太网交换机上。\r\n3. 以太网交换机对该请求进行泛洪,发送到其包括Eth1在内的所有端口上。\r\n4. 由于Eth1被加入了VPN主机上的Linux Bridge,因此Linux Bridge收到该ARP请求。\r\n5. Linux Bridge对该ARP请求进行泛洪,发送到连到其上面的Tap虚拟网卡上。\r\n6. VPN程序通过/dev/net/tun字符设备读取到该ARP请求,然后封装到TCP/UDP包中,发送到对端站点的VPN主机。\r\n7. 对端站点的VPN程序通过监听TCP/UDP端口接收到封装的ARP请求,将ARP请求通过/dev/net/tun字符设备写入到Tap设备中。\r\n8. Linux Bridge泛洪,将ARP请求发送往Eth1,由于Eth1连接到了以太网交换机上,以太网交换机接收到了该ARP请求。\r\n9. 以太网交换机进行泛洪,将ARP请求发送给了包括192.168.0.3的所有主机。\r\n10. 192.168.0.3收到了APR请求,判断iP地址和自己相同,对此请求进行响应。\r\n11. 同理,ARP响应包也可以按照该路径返回到图左边包括192.168.0.5在内的站点中。\r\n\r\n从站点主机的角度来看,上面图中两个VPN主机之间的远程连接可以看作一条虚拟的网线,这条网线将两个Linux Bridge连接起来。这两个Linux Bridge和两个以太网交换机一起将左右两个站点的主机连接在一起,形成了一个局域网。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411152158051.png)\r\n\r\n# 4. 参考资料\r\n[Linux Tun/Tap 介绍-赵化冰的博客 | Zhaohuabing Blog](https://www.zhaohuabing.com/post/2020-02-24-linux-taptun/)"},{"id":"Vxlan隧道配置","title":"Vxlan隧道配置","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":3,"slug":"vxlan隧道配置","description":"1. 主机应用通过绑定独立路由表或vrf的方式通过隧道访问对端 > 图为网图,本文配置vtep地址换成了10.0.0.250和10.0.0.251 250主机配置 查看当前系统信息 > 虽然从概念上讲,VRF 主要用于实现逻辑上的路由隔离,但在系统的网络配置和管理中,它被呈现为一个类似普通网络接口的...","relativePath":"Tech/Os/Linux/Network/Vxlan隧道配置.md","rawContent":"\r\n# 1. 主机应用通过绑定独立路由表或vrf的方式通过隧道访问对端\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162021224.png)\r\n> 图为网图,本文配置vtep地址换成了10.0.0.250和10.0.0.251\r\n\r\n250主机配置\r\n```shell\r\n# 配置vxlan隧道\r\nip link add vxlan0 type vxlan \\\r\nid 40 \\\r\ndstport 4789 \\\r\nremote 10.0.0.251 \\\r\nlocal 10.0.0.250 \\\r\ndev eth0\r\n# 创建了一个名为vrf0的虚拟路由和转发(VRF)实例,并指定其路由表为10。VRF允许在同一物理设备上创建多个逻辑上独立的路由和转发域。\r\nip link add vrf0 type vrf table 10 \r\nip link set vrf0 up\r\n# 将名为vxlan0的网络接口设置为vrf0的从属接口,意味着vxlan0接口的流量将在vrf0所定义的路由和转发规则下进行处理。\r\nip link set vxlan0 master vrf0 \r\n# 给vxlan0口配置ip并启用\r\nip addr add 172.18.1.2/24 dev vxlan0 \r\nip link set vxlan0 up\r\n```\r\n\r\n查看当前系统信息\r\n```\r\nroot@iZbp1apxihjwrp6fggnpqqZ:~# ip vrf show \r\nName Table ----------------------- \r\nvrf0 10\r\nroot@iZbp1apxihjwrp6fggnpqqZ:~# ip route show vrf vrf0 \r\n172.18.1.0/24 dev vxlan0 proto kernel scope link src 172.18.1.2\r\n```\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162020855.png)\r\n\r\n> 虽然从概念上讲,VRF 主要用于实现逻辑上的路由隔离,但在系统的网络配置和管理中,它被呈现为一个类似普通网络接口的实体,您可以为其分配 IP 地址、设置状态(如启用或禁用)等,这就是为什么使用 `ip a` 命令能够看到 `vrf0` 。\r\n\r\n251上配置\r\n```shell\r\nip link add vxlan0 type vxlan \\\r\nid 40 \\\r\ndstport 4789 \\\r\nremote 10.0.0.250 \\\r\nlocal 10.0.0.251 \\\r\ndev eth0\r\nip link add vrf0 type vrf table 10\r\nip link set vrf0 up\r\nip link set vxlan0 master vrf0\r\nip addr add 172.18.1.3/24 dev vxlan0\r\nip link set vxlan0 up\r\n```\r\n\r\n10.0.0.250节点上ping测试\r\n```shell\r\nroot@iZbp1apxihjwrp6fggnpqqZ:~# ping 172.18.1.3 -I vxlan0 \r\nPING 172.18.1.3 (172.18.1.3) from 172.18.1.2 vxlan0: 56(84) bytes of data. \r\n64 bytes from 172.18.1.3: icmp_seq=1 ttl=64 time=0.270 ms \r\n64 bytes from 172.18.1.3: icmp_seq=2 ttl=64 time=0.217 ms \r\n64 bytes from 172.18.1.3: icmp_seq=3 ttl=64 time=0.240 ms\r\n```\r\n\r\n10.0.0.250 eth0口抓包\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162026826.png)\r\n10.0.0.250 vxlan0口抓包\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162030967.png)\r\n10.0.0.251 eth0抓包\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162028704.png)\r\n10.0.0.251 vxlan0口抓包\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162031675.png)\r\n\r\n现代很多网卡支持硬件卸载能力,tcp/udp校验和的计算可以交给网卡硬件去算,减轻CPU负担。所以抓包位置在网卡完成硬件卸载之前可能会看到bad udp cksum。\r\n## 1.1. 通过路由配置三层转发\r\n10.0.0.251主机添加\r\n```\r\nip addr add 192.168.0.10/24 dev vxlan0\r\n```\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162100163.png)\r\n\r\n10.0.0.250主机只需要添加路由即可\r\n```shell\r\nip route add default dev vxlan0 table 10 vrf vrf0\r\n```\r\n查看路由表\r\n```shell\r\nroot@iZbp1apxihjwrp6fggnpqqZ:~# ip route show table 10 \r\ndefault dev vxlan0 scope link\r\n172.18.1.0/24 dev vxlan0 proto kernel scope link src \r\n172.18.1.2 local 172.18.1.2 dev vxlan0 proto kernel scope host src 172.18.1.2 \r\nbroadcast 172.18.1.255 dev vxlan0 proto kernel scope link src 172.18.1.2\r\n```\r\n\r\n可以看到我们实现了在10.0.0.250主机中通过vxlan隧道访问三层任意目的ip\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162103945.png)\r\n\r\n有趣的是,我们甚至在10.0.0.251主机上的vxlan0口添加10.0.0.253\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411162114869.png)\r\n\r\n# 2. docker在独立name space中访问对端\r\n在10.0.0.250上配置隧道\r\n```shell\r\ndocker run -itd --name container-client ubuntu\r\napt update\r\napt install net-tools -y\r\napt install iputils-ping -y\r\napt install iproute2 -y\r\n\r\n# 首先,查看容器主进程的 PID,然后将其保存到$pid变量中\r\ndocker inspect --format '' container-client\r\npid=$(docker inspect --format '' container-client)\r\n\r\n# 将该pid链接至/var/run/netns 中,以便ip netns命令可以访问到它\r\nsudo mkdir -p /var/run/netns\r\nsudo ln -s /proc/$pid/ns/net /var/run/netns/$pid\r\n\r\n# 创建并启用vxlan隧道,并将隧道放到容器ns中\r\nip link add vxlan0 type vxlan \\\r\nid 40 \\\r\ndstport 4789 \\\r\nremote 10.0.0.251 \\\r\nlocal 10.0.0.250 \\\r\ndev eth0\r\nip link set vxlan0 up\r\nsudo ip link set vxlan0 netns $pid\r\nsudo ip netns exec $pid ip addr add 172.18.1.2/24 dev vxlan0\r\n\r\n# 添加默认路由指向隧道口\r\nsudo ip netns exec $pid route add default dev vxlan0\r\n```\r\n\r\n在 Docker的Bridge 模式下,`docker0` 网桥位于主机的默认网络命名空间中。在Docker中,当创建一个容器并使用默认的Bridge网络模式时,Docker会为容器创建一对虚拟网络接口(veth pair)。其中一端eth0(容器中的eth0,非宿主机中eth0)连接到容器内部,另一端连接到主机上的`docker0` 网桥。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411171547181.png)\r\n配置路由默认走vxlan口\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411171548591.png)\r\n\r\n这里我们10.0.0.251保持上节中的配置不变,当然你也可以用在10.0.0.251中配置相同的容器网络。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411171550387.png)\r\n\r\n## 2.1. 抓包分析\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411171551304.png)\r\n\r\nping 172.18.1.3\r\n1. vxlan0口(172.18.1.2)arp请求172.18.1.3\r\n2. 封装vxlan隧道发送到对端\r\n3. 对端解封装vxlan隧道并arp reply\r\n4. 封装icmp request\r\n5. 封装vxlan隧道发送到对端\r\n6. 对端解封装vxlan隧道并icmp reply\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411171600981.png)\r\n\r\n# 3. 只走隧道网络的容器\r\n容器默认会连接到docker0网桥上并分配ip,如果我们的容器仅需要通过vxlan与其他主机通信,可以只配置vxlan0口即可。\r\n\r\n自定义Dockerfile\r\n```dockerfile\r\nFROM ubuntu:latest\r\n\r\nRUN apt-get update && \\\r\n apt-get install -y iproute2 iputils-ping net-tools curl traceroute telnet\r\n\r\nCMD [\"bash\"]\r\n```\r\n\r\n```shell\r\ndocker build -t ubuntu-with-network-tools .\r\ndocker run -itd --network none --name container-client ubuntu-with-network-tools\r\n```\r\n\r\n容器中只有vxlan0口\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202411171622801.png)\r\n\r\n\r\n\r\n\r\n# 4. 参考文献\r\n[VXLAN 基础教程:在 Linux 上配置 VXLAN 网络上篇文章结尾提到 Linux 是支持 VXLAN 的,我们 - 掘金 (juejin.cn)](https://juejin.cn/post/6844904133430870029)\r\n[在容器中搭建简单的 Vxlan 隧道 | 政子的博客 (zhengzi.me)](https://blog.zhengzi.me/vxlan_tunnel_with_container/)"},{"id":"管道","title":"管道","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"管道","description":"1. client 2. server 3. ouput 数据直接在内存中的管道缓冲区传输","relativePath":"Tech/Os/Linux/Network/管道.md","rawContent":"# 1. client\r\n```python\r\n# 客户端代码\r\nimport subprocess\r\nimport json\r\n\r\n# 启动服务器进程(创建两个管道)\r\nprocess = subprocess.Popen(\r\n ['python', 'server.py'],\r\n stdin=subprocess.PIPE,\r\n stdout=subprocess.PIPE,\r\n text=True\r\n)\r\n\r\n# 发送请求(写入stdin管道)\r\nrequest = {\"jsonrpc\": \"2.0\", \"method\": \"tools/list\", \"id\": 1}\r\nprocess.stdin.write(json.dumps(request) + '\\n')\r\nprocess.stdin.flush()\r\n\r\n# 接收响应(读取stdout管道)\r\nresponse_line = process.stdout.readline()\r\nresponse = json.loads(response_line)\r\nprint(response)\r\n```\r\n# 2. server\r\n```python\r\n# 服务器代码\r\nimport sys\r\nimport json\r\n\r\ndef handle_request(request):\r\n # 简单返回一个工具列表作为示例\r\n if request.get(\"method\") == \"tools/list\":\r\n return [\"tool1\", \"tool2\"]\r\n return None\r\n\r\n# 接收请求(读取stdin管道)\r\nrequest_line = sys.stdin.readline()\r\nrequest = json.loads(request_line)\r\n\r\n# 处理请求\r\nresult = handle_request(request)\r\n\r\n# 发送响应(写入stdout管道)\r\nresponse = {\r\n \"jsonrpc\": \"2.0\",\r\n \"result\": result,\r\n \"id\": request.get(\"id\")\r\n}\r\nsys.stdout.write(json.dumps(response) + '\\n')\r\nsys.stdout.flush()\r\n```\r\n# 3. ouput\r\n```\r\n{'jsonrpc': '2.0', 'result': ['tool1', 'tool2'], 'id': 1}\r\n```\r\n数据直接在内存中的管道缓冲区传输\r\n```\r\n服务器进程 客户端进程\r\n ↓ ↑\r\nprint(\"data\") readline()\r\n ↓ ↑\r\nstdout buffer → pipe buffer → stdin buffer\r\n (进程内存) (内核空间) (进程内存)\r\n```\r\n"},{"id":"网卡混杂","title":"网卡混杂","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"网卡混杂","description":"网卡在混杂模式下不会校验收到的报文的mac和ip","relativePath":"Tech/Os/Linux/Network/网卡混杂.md","rawContent":"网卡在混杂模式下不会校验收到的报文的mac和ip\r\n```\r\nip link set eth0 promisc off\r\nip link set eth0 promisc on\r\n```"},{"id":"CPU使用率","title":"CPU使用率","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"cpu使用率","description":"1. 查看进程CPU使用率 1.1. 平均使用率 ps命令显示的值是进程的平均CPU使用率,计算公式为: $\\%CPU=(进程使用的CPU时间​/进程运行的总时间)×100$ 如下命令显示CPU占用最高的前几名进程 输出如下 1.2. 瞬时使用率 如果想监控某个进程(比如 )的 CPU 使用率 ...","relativePath":"Tech/Os/Linux/常用运维命令/CPU使用率.md","rawContent":"# 1. 查看进程CPU使用率\r\n## 1.1. 平均使用率\r\nps命令显示的`%cpu`值是进程的平均CPU使用率,计算公式为:\r\n$\\%CPU=(进程使用的CPU时间​/进程运行的总时间)×100$\r\n如下命令显示CPU占用最高的前几名进程\r\n`ps -eo pid,comm,%cpu --sort=-%cpu | head`\r\n输出如下\r\n```scss\r\nPID COMMAND %CPU \r\n1234 firefox 42.3 \r\n5678 chrome 37.5 \r\n9101 java 22.0\r\n```\r\n## 1.2. 瞬时使用率\r\n如果想监控某个进程(比如 `nginx`)的 CPU 使用率\r\n```\r\ntop -p $(pgrep nginx)\r\n```\r\n# 2. 查看系统CPU使用率\r\n## 2.1. 使用 `top` 命令(实时查看)\r\n进入 `top` 界面后,按 **`1`** 可以查看每个CPU核心的使用情况。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302162105169.png)\r\n`Cpu(s)` 行的字段含义:\r\n- **`us`(user)**:用户进程占用 CPU 百分比\r\n- **`sy`(system)**:内核进程占用 CPU 百分比\r\n- **`ni`(nice)**:调整过优先级的用户进程占用 CPU 百分比\r\n- **`id`(idle)**:空闲 CPU 百分比\r\n- **`wa`(iowait)**:等待 I/O 操作的时间\r\n- **`hi`(hardware IRQ)**:硬件中断占用 CPU 百分比\r\n- **`si`(software IRQ)**:软件中断占用 CPU 百分比\r\n- **`st`(steal)**:虚拟化环境中被其他虚拟机抢占的 CPU 百分比\r\n```scss\r\n%Cpu(s): 12.3 us, 4.5 sy, 0.0 ni, 80.1 id, 2.9 wa, 0.0 hi, 0.2 si, 0.0 st\r\n```\r\n- 用户进程占用12.3%CPU\r\n- 内核进程占用4.5%CPU\r\n- 空闲80.1%CPU\r\n## 2.2. 使用 `htop`(更美观的交互界面)\r\n需要手动安装(部分 Linux 发行版可能没有预装)。\r\n```shell\r\nsudo apt install htop \r\n# Ubuntu/Debian \r\nsudo yum install htop \r\n# CentOS/RHEL sudo dnf install htop # Fedora`\r\n```\r\n- 显示每个 CPU 核心的负载情况\r\n- 颜色区分不同类型的 CPU 负载\r\n- 支持鼠标操作和快捷键\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302162144690.png)\r\n## 2.3. 使用 `vmstat`(监控 CPU 使用情况)\r\n`vmstat 1`\r\n输出如下\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302162217345.png)\r\n- **`us`(user)**:用户进程CPU使用率\r\n- **`sy`(system)**:内核进程CPU使用率\r\n- **`id`(idle)**:CPU空闲率\r\n- **`wa`(wait)**:I/O等待占用CPU比例\r\n## 2.4. 使用 `mpstat`(更详细的 CPU 统计信息)\r\n`mpstat -P ALL 1`\r\n**关键参数**\r\n- `-P ALL`:显示所有CPU核心的统计信息\r\n- `1`:每秒刷新一次\r\n安装方式\r\n```shell\r\nsudo apt install sysstat \r\n# Ubuntu/Debian \r\nsudo yum install sysstat \r\n# CentOS/RHEL`\r\n```\r\n输出如下\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302162355778.png)\r\n## 2.5. 使用 `sar`(历史 CPU 使用情况)\r\n`sar -u 5 3`\r\n- 需要安装 `sysstat`(同 `mpstat`)\r\n- `-u`:显示 CPU 使用情况。\r\n- `5 3`:每 5 秒采样一次,共 3 次。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302162516258.png)\r\ntop是实时显示cpu使用率,而sar适用于**CPU 历史使用情况分析**,可以查看过去的CPU负载趋势。sar从 `sysstat` 日志文件 `/var/log/sysstat/` 读取数据,适合长期分析。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250302162750842.png)\r\n## 2.6. 只查看 CPU 使用率数值\r\n如果你只想获取当前的CPU使用率(去掉 `idle` 部分):\r\n`top -bn1 | grep \"Cpu(s)\" | awk '{print 100 - $8\"%\"}'`\r\n- `$8` 代表 `idle` 值(空闲 CPU)。\r\n- 计算 `100 - idle`,得到 CPU 使用率。\r\n```scss\r\nroot@vultr:~# top -bn1 | grep \"Cpu(s)\" | awk '{print 100 - $8\"%\"}'\r\n16.7%\r\n```\r\n# 3. 总结\r\n\r\n| 命令 | 作用 |\r\n| ---------------------------------- | -------------------------- |\r\n| `top` | 实时查看CPU平均使用率,按 `1` 显示各核心情况 |\r\n| `htop` | 交互式界面,类似`top`,更美观 |\r\n| `vmstat 1` | 每秒显示一次CPU统计信息 |\r\n| `mpstat -P ALL 1` | 按 CPU 核心统计使用率 |\r\n| `sar -u 5 3` | 采样 CPU 负载历史数据 |\r\n| `top -bn1 | grep \"Cpu(s)\" |\r\n| \"ps -eo pid,comm,%cpu --sort=-%cpu \\| head\" | 查看CPU实时利用率 |\r\n| `ps -C nginx -o %cpu` | 查询 `nginx` 进程的 CPU 使用率 |\r\n"},{"id":"ls","title":"Ls","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"ls","description":"1. 基于文件名排序 2. 基于文件大小排序 3. 基于文件时间排序","relativePath":"Tech/Os/Linux/常用运维命令/ls.md","rawContent":"```shell\r\nroot@vultr:~# ls -trl --full-time\r\ntotal 232\r\n-rw-r--r-- 1 root root 432 2024-11-15 13:46:40.747000144 +0000 server.py\r\n-rw-r--r-- 1 root root 358 2024-11-15 13:47:40.179788313 +0000 client.py\r\n-rwxr-xr-x 1 root root 19975 2024-12-13 14:11:47.433975727 +0000 trojan.sh\r\n-rwxr-xr-x 1 root root 8399 2024-12-13 14:23:52.420202666 +0000 easytrojan.sh\r\n-rw-r--r-- 1 root root 171012 2025-01-01 15:50:17.668060999 +0000 menu.sh\r\n-rwxr-xr-x 1 root root 24301 2025-02-26 14:58:13.775206307 +0000 tcp.sh\r\nroot@vultr:~# ls -Srlh\r\ntotal 232K\r\n-rw-r--r-- 1 root root 358 Nov 15 13:47 client.py\r\n-rw-r--r-- 1 root root 432 Nov 15 13:46 server.py\r\n-rwxr-xr-x 1 root root 8.3K Dec 13 14:23 easytrojan.sh\r\n-rwxr-xr-x 1 root root 20K Dec 13 14:11 trojan.sh\r\n-rwxr-xr-x 1 root root 24K Feb 26 14:58 tcp.sh\r\n-rw-r--r-- 1 root root 168K Jan 1 15:50 menu.sh\r\n```\r\n# 1. 基于文件名排序\r\n```bash\r\n# ls -fl\r\n```\r\n# 2. 基于文件大小排序\r\n```bash\r\n# ls -Sr\r\n```\r\n# 3. 基于文件时间排序\r\n```bash\r\n# ls -tr \r\n```\r\n"},{"id":"Lsof","title":"Lsof","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"lsof","description":"(List Open Files)是一个用于列出系统中打开文件的工具。这里的 “文件” 是一个广义的概念,包括常规文件、目录、块设备、字符设备、共享库、网络套接字等。会列出系统中当前用户打开的所有文件。 : 列出指定进程 ID 的进程打开的文件 :列出指定用户打开的文件 : 列出正在执行指定命令的进...","relativePath":"Tech/Os/Linux/常用运维命令/Lsof.md","rawContent":"`lsof`(List Open Files)是一个用于列出系统中打开文件的工具。这里的 “文件” 是一个广义的概念,包括常规文件、目录、块设备、字符设备、共享库、网络套接字等。`lsof`会列出系统中当前用户打开的所有文件。\r\n\r\n`-p `: 列出指定进程 ID 的进程打开的文件\r\n`-u `:列出指定用户打开的文件\r\n`-c `: 列出正在执行指定命令的进程打开的文件\r\n`-i [protocol][@hostname|hostaddr][:service|port]`: 示例:`lsof -i TCP:80`\r\n\r\n如果要查看fd的创建时间,则可以进入`/proc/${pid}/fd/$fdid`查看文件的创建时间"},{"id":"PS","title":"PS","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":11,"slug":"ps","description":"命令是process status的简称,用于显示当前运行的进程的信息。在不使用任何标识的情况下,会显示所有当前用户启动的进程,比如: - PID: 进程的ID号 - TTY: 终端名称缩写 - TIME: CPU时间,即进程使用CPU的总时间 - CMD: 所执行的命令名称。 1. 参数 命令支持...","relativePath":"Tech/Os/Linux/常用运维命令/PS.md","rawContent":"`ps`命令是process status的简称,用于显示当前运行的进程的信息。在不使用任何标识的情况下,`ps`会显示所有当前用户启动的进程,比如:\r\n```shell\r\n$ ps\r\n  PID TTY          TIME CMD\r\n 9961 pts/0    00:00:00 bash\r\n 9981 pts/0    00:00:00 ps\r\n```\r\n- PID: 进程的ID号\r\n- TTY: 终端名称缩写\r\n- TIME: CPU时间,即进程使用CPU的总时间\r\n- CMD: 所执行的命令名称。\r\n# 1. 参数\r\n`ps`命令支持的参数很多,这里我们只列出常用的一些。\r\n- `a`显示当前终端所有进程\r\n- `-A`显示系统所有进程\r\n- `e`显示每个进程使用的环境变量\r\n- `-e`显示所有进程,等价于`-A`\r\n- `-f`显示进程之间关系\r\n- `-H`显示树桩结构\r\n- `u`显示进程的归属用户及内存的使用情况\r\n- `x`显示没有控制终端的进程\r\n# 2. 常见使用方式\r\n## 2.1. ps -ef\r\n显示所有进程的pid、启动命令和父进程pid,常配合管道符`|`和`grep`来查找某个特定进程。\r\n```shell\r\n  UID   PID  PPID   C STIME   TTY           TIME CMD\r\n    0     1     0   0 11:36AM ??         1:18.88 /sbin/launchd\r\n    0    85     1   0 11:36AM ??         1:16.91 /usr/libexec/logd\r\n    0    86     1   0 11:36AM ??         0:06.62 /usr/libexec/UserEventAgent (System)\r\n    0    89     1   0 11:36AM ??         0:01.04 /System/Library/PrivateFrameworks/Uninstall.framework/Resources/uninstalld\r\n```\r\n- UID:启动用户ID\r\n- PID:进程ID\r\n- PPID:父进程 ID\r\n- C:CPU用于计算执行优先级的因子。数值越大,表明进程是CPU密集型运算,执行优先级会降低;数值越小,表明进程是I/O密集型运算,执行优先级会提高。\r\n- STIME:进程启动的时间\r\n- TTY:终端名称缩写\r\n- TIME:CPU时间,即进程使用CPU的总时间\r\n- CMD:启动进程所用的命令和参数\r\n## 2.2. ps –aux\r\n适合于需要查看进程更多的详细信息,包括系统资源占用情况、进程状态等。\r\n```shell\r\nUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\r\nroot 1 0.0 0.8 77664 8668 ? Ss 2022 1:08 /sbin/init\r\nroot 2 0.0 0.0 0 0 ? S 2022 0:05 [kthreadd]\r\nroot 4 0.0 0.0 0 0 ? I< 2022 0:00 [kworker/0:0H]\r\nroot 6 0.0 0.0 0 0 ? I< 2022 0:00 [mm_percpu_wq]\r\n```\r\n- USER:用户名称\r\n- PID:进程号\r\n- %CPU:进程占用CPU的百分比\r\n- %MEM:进程占用物理内存的百分比\r\n- VSZ:进程占用的虚拟内存大小(单位:KB)\r\n- RSS:进程占用的物理内存大小(单位:KB)\r\n- TTY:终端名称缩写\r\n- STAT:进程状态,其中 `S(Sleep)可中断,`s-表示该进程是会话的先导进程。\r\n- STARTED:进程的启动时间\r\n- TIME:CPU时间,即进程使用CPU的总时间\r\n- COMMAND:启动进程所用的命令和参数,如果过长会被截断显示\r\n## 2.3. ps -o\r\n`ps -o` 是 `ps` 命令的一个强大用法,`ps` 命令用于显示当前系统的进程状态,而 `-o` 选项可以让你自定义要显示的进程信息列,从而满足不同的查看需求。\r\n基本语法为:\r\n```shell\r\nps -o 列名1,列名2,列名3...\r\n```\r\n- **`pid`**:进程的唯一标识符。\r\n- **`user`**:启动该进程的用户。\r\n- **`%cpu`**:进程占用 CPU 的百分比。\r\n- **`%mem`**:进程占用内存的百分比。\r\n- **`vsz`**:进程使用的虚拟内存大小(单位:KB)。\r\n- **`rss`**:进程使用的物理内存大小(单位:KB)。\r\n- **`tty`**:进程关联的终端设备。\r\n- **`stat`**:进程的状态,如 `S`(睡眠)、`R`(运行)、`Z`(僵尸)等。\r\n- **`start`**:进程启动的时间。\r\n- **`time`**:进程占用 CPU 的总时间。\r\n- **`cmd`**:启动进程的命令。\r\n- **`psr`**:显示进程当前运行在哪个 CPU 核心上(CPU 编号)。\r\n```shell\r\n# ps ax -o pid,comm,%cpu,%mem,psr\r\n PID COMMAND %CPU %MEM PSR\r\n 1 systemd 0.0 1.0 0\r\n 2 kthreadd 0.0 0.0 0\r\n```\r\n## 2.4. ps排序\r\n使用`--sort`进行排序,格式如`--sort=[+|-] key`,+代表升序,-代表降序\r\n```shell\r\n# 按内存升序排列:\r\nps aux --sort=rss \r\n# 按CPU降序排列\r\nps aux --sort=-%cpu\r\n```\r\n# 3. 指标解释\r\n## 3.1. CPU使用率\r\n- **Elapsed CPU usage(累计 CPU 使用率)**:指的是从某个特定的起始时间点到当前时刻,进程或系统使用 CPU 的总时间占比。它反映了在一段较长时间内 CPU 资源的累计消耗情况。例如,一个进程从启动到现在已经运行了 10 分钟,在这 10 分钟内它总共占用了 CPU 5 分钟的时间,那么它的 elapsed CPU usage 就是 50%。通过这个指标,可以了解一个进程或系统在一段时间内对 CPU 资源的整体占用程度,有助于分析长期的资源使用趋势和性能表现。\r\n- **Recent CPU usage(最近 CPU 使用率)**:通常是指在最近的一个较短时间间隔内,进程或系统使用 CPU 的时间占比。这个时间间隔可以是几秒钟、几分钟等,具体取决于测量的频率和设置。例如,测量最近 1 分钟内某个进程使用 CPU 的时间,如果在这 1 分钟内该进程占用了 CPU 30 秒,那么它的 recent CPU usage 就是 50%。该指标更侧重于反映当前或近期的CPU使用情况,能够及时捕捉到进程或系统对 CPU 资源需求的快速变化,对于实时监控和及时发现性能问题非常有帮助。例如,当系统出现卡顿或响应变慢时,查看 recent CPU usage 可以快速确定是否是由于某个进程在近期突然占用了大量的 CPU 资源导致的。\r\n## 3.2. TTY\r\n`TTY` 是 “TeleTYpewriter” 的缩写,它表示进程所关联的终端设备。具体含义如下:\r\n- 如果显示为一个具体的终端设备名称,如 `pts/0`、`tty1` 等,说明该进程是通过这个终端启动的,或者与这个终端有交互。其中,`pts/` 表示伪终端,通常用于远程登录或通过终端模拟器启动的进程;`tty` 表示物理终端或虚拟控制台。\r\n- 如果显示为 `?`,则表示该进程没有关联到任何终端设备,这类进程通常是作为后台服务或守护进程运行的,它们在系统启动时自动启动,不需要用户通过终端进行交互操作。\r\n**作用**:通过 `TTY` 信息,可以了解进程与终端的关联情况,有助于判断进程的启动方式和运行环境。例如,当你看到一个进程的 `TTY` 是 `pts/0`,就知道它是通过某个远程连接或终端模拟器启动的,而 `TTY` 为 `?` 的进程通常是在后台默默运行,不依赖于用户的终端输入输出。\r\n# 4. ref\r\n1. -A Display information about other users' processes, including those without controlling terminals. 列出所有进程(包括其他用户或无控制终端的进程)\r\n2. -e Identical to -A.\r\n3. -a Display information about other users' processes as well as your own. This will skip any processes which do not have a controlling terminal, unless the -x option is also specified. 列出所有进程(包括其他用户,但是不包含没有控制终端的进程)。`-ax`组合使用时等效于`-A`\r\n4. -c Change the “command” column output to just contain the executable name, rather than the full command line. COMMAND列仅包含可执行文件的名称,而不包含完整的命令行\r\n5. -E Display the environment as well. This does not reflect changes in the environment after process launch. 显示进程的环境变量\r\n6. -X When displaying processes matched by other options, skip any processes which do not have a controlling terminal. 不显示没有控制终端的进程。\r\n7. -x When displaying processes matched by other options, include processes which do not have a controlling terminal. This is the opposite of the -X option. If both -X and -x are specified in the same command, then ps will use the one which was specified last. 和-X相反,显示包括没有控制终端的进程。-x和-X一起用时,以最后出现的那个选项为准.\r\n## 4.1. 输出详细信息\r\n1. -f Display the uid, pid, parent pid, recent CPU usage, process start time, controlling tty, elapsed CPU usage, and the associated command. If the -u option is also used, display the user name rather then the numeric uid. When -o or -O is used to add to the display following -f, the command field is not truncated as severely as it is in other formats. 显示UID,PID,PPID,最近CPU使用率,进程启动时间,tty,累计CPU使用率以及关联的命令。\r\n2. -v Display information associated with the following keywords: pid, state, time, sl, re, pagein, vsz, rss, lim, tsiz, %cpu, %mem, and command. The -v option implies the -m option. `-v`参数隐含了`-m`参数\r\n3. -w Use 132 columns to display information, instead of the default which is your window size. If the -w option is specified more than once, ps will use as many columns as necessary without regard for your window size. When output is not to a terminal, an unlimited number of columns are always used. 默认情况下,`ps`命令输出的每行内容宽度有限,当进程的命令行参数、环境变量等信息较长时,超出部分会被截断显示。使用`-w`参数后,可使输出的宽度增加,让更多信息完整呈现。多次使用`-ww`可以累加\r\n4. -l Display information associated with the following keywords: uid, pid, ppid, flags, cpu, pri, nice, vsz=SZ, rss, wchan, state=S, paddr=ADDR, tty, time, and command=CMD.\r\n## 4.2. 指定用户或用户组\r\n1. -U Display the processes belonging to the specified real user IDs.\r\n2. -u Display the processes belonging to the specified usernames.\r\n3. -G Display information about processes which are running with the specified real group IDs.\r\n4. -g Display information about processes with the specified process group leaders."},{"id":"sort","title":"Sort","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"sort","description":"-f :忽略大小写的差异,例如 A 与 a 视为编码相同; -b :忽略最前面的空格符部分; -M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法; -n :使用『纯数字』进行排序(默认是以文字型态来排序的); -r :反向排序; -u :就是 uniq ,去重,相同的数据中,仅出现一...","relativePath":"Tech/Os/Linux/常用运维命令/sort.md","rawContent":"-f :忽略大小写的差异,例如 A 与 a 视为编码相同;\r\n-b :忽略最前面的空格符部分;\r\n-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;\r\n-n :使用『纯数字』进行排序(默认是以文字型态来排序的);\r\n-r :反向排序;\r\n-u :就是 uniq ,去重,相同的数据中,仅出现一行代表;\r\n-t :分隔符,默认是用 [tab] 键来分隔;\r\n-k :以那个区间 (field) 来进行排序的意思\r\n# 1. 示例\r\n排序\r\n```shell\r\n╭─zhangrui@zhangruideMacBook-Pro ~/test\r\n╰─$ ll |sort -r\r\ntotal 0\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:41 ba\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:41 a1\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:41 1a\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:41 1\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:40 b\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:40 ab\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:40 a\r\n╭─zhangrui@zhangruideMacBook-Pro ~/test\r\n╰─$ ll |sort\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:40 a\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:40 ab\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:40 b\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:41 1\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:41 1a\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:41 a1\r\n-rw-r--r-- 1 zhangrui staff 0B Mar 20 23:41 ba\r\ntotal 0\r\n```\r\n去重排序\r\n```shell\r\n╭─zhangrui@zhangruideMacBook-Pro ~/test\r\n╰─$ cat test_file\r\na\r\nb\r\nc\r\nb\r\na\r\n╭─zhangrui@zhangruideMacBook-Pro ~/test\r\n╰─$ cat test_file|sort -r\r\nc\r\nb\r\nb\r\na\r\na\r\n╭─zhangrui@zhangruideMacBook-Pro ~/test\r\n╰─$ cat test_file|sort -ru\r\nc\r\nb\r\na\r\n```"},{"id":"常用运维命令","title":"常用运维命令","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"常用运维命令","description":"","relativePath":"Tech/Os/Linux/常用运维命令/常用运维命令.md","rawContent":""},{"id":"核隔离和cgroup","title":"核隔离和Cgroup","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":5,"slug":"核隔离和cgroup","description":"!CPU1. 基本概念 1. 核隔离 修改内核启动参数 1.1. CPU加压测试 htop可以看到隔离的核没有受到影响 2. 绑定进程到隔离核心 3. cgroup 输出如下 > [!info] > Worker 0 started on CPU 2 Worker 2 started on CPU ...","relativePath":"Tech/Os/Linux/核隔离和cgroup.md","rawContent":"\r\n![[CPU#1. 基本概念]]\r\n# 1. 核隔离\r\n修改内核启动参数\r\n```shell\r\n# 查看可用cpu编号\r\nlscpu | grep -i on-line\r\n# 修改内核启动参数 vim /etc/default/grub\r\nGRUB_CMDLINE_LINUX=\"isolcpus=2,3 ...其他参数...\" # 多个参数用空格分隔\r\n# 更新grub并重启,centos或redhat系统使用sudo grub2-mkconfig -o /boot/grub2/grub.cfg\r\nsudo update-grub \r\nsudo reboot\r\n# 查看被隔离CPU\r\ncat /sys/devices/system/cpu/isolated\r\n# 2-3\r\n# 或使用以下命令\r\ncat /proc/cmdline | grep isolcpus\r\n```\r\n## 1.1. CPU加压测试\r\n```shell\r\n# 安装stress\r\nsudo apt-get update\r\nsudo apt-get install stress\r\n# 指定cpu数量\r\nstress --cpu 4\r\n```\r\nhtop可以看到隔离的核没有受到影响\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250222000430884.png)\r\n# 2. 绑定进程到隔离核心\r\n```shell\r\ntaskset -c 2 stress --cpu 1 &\r\ntaskset -c 3 stress --cpu 2 &\r\n```\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250222001417888.png)\r\n# 3. cgroup\r\n```bash\r\n# 创建cgroup目录\r\nsudo mkdir /sys/fs/cgroup/cpuset/myapp\r\n# 设置允许使用的CPU核心(2-3)\r\necho \"2-3\" | sudo tee /sys/fs/cgroup/cpuset/myapp/cpuset.cpus\r\n# 设置关联的内存节点(通常为0)\r\necho \"0\" | sudo tee /sys/fs/cgroup/cpuset/myapp/cpuset.mems\r\n# 启动程序并加入cgroup\r\nsudo cgexec -g cpuset:myapp ./cpu_test\r\n```\r\n输出如下\r\n> [!info]\r\n> Worker 0 started on CPU 2\r\nWorker 2 started on CPU 2\r\nWorker 1 started on CPU 3\r\nWorker 3 started on CPU 3\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250223224921767.png)\r\n\r\n示例代码参考[[核隔离和cgroup#5. 附录]]\r\n\r\n> [!tip]\r\n> 这里一定要设置cpu affinity,否则进程偏向于只调度在一个CPU上\r\n# 4. irqbalance\r\nirqbalance是一个 Linux 系统中的守护进程,其主要功能是动态地将硬件中断请求(IRQ)分配到不同的CPU核心上,以此来优化系统的中断处理性能。在多CPU核心的系统中,如果所有的中断都集中在一个或少数几个CPU 核心上处理,会导致这些核心负载过高,而其他核心则处于空闲状态,从而影响系统整体性能。`irqbalance` 会根据系统的负载情况,自动调整中断的分配,使得中断能够均匀地分布到各个CPU核心上,提高系统的并行处理能力和响应速度。\r\n## 4.1. 禁用中断\r\n由于 `irqbalance` 会动态调整中断分配,可能会导致中断在不同的 CPU 核心之间频繁迁移。这种中断迁移会带来额外的开销,例如缓存失效、上下文切换等,从而导致系统性能出现波动,不利于DPDK应用程序实现稳定的高性能处理。在基于DPDK的网络转发平台上,我们可以设置禁止转发线程使用CPU处理中断,以防止造成性能损失。\r\n## 4.2. /etc/sysconfig/irqbalance文件\r\n该文件用于存储 `irqbalance` 服务的配置参数,通过修改这个文件中的配置项,可以对 `irqbalance` 服务的行为进行定制化设置。\r\n## 4.3. 禁止CPU处理中断\r\n`vim /etc/sysconfig/irqbalance`,添加`IRQBALANCE_BANNED_CPUS=c`,[参考设置](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_for_real_time/7/html/tuning_guide/interrupt_and_process_binding#Interrupt_and_process_binding)\r\n\r\n> [!tip] \r\n> 这里的`C`是一个十六进制数,转换为二进制是 `1100`,从右到左每一位依次对应 CPU 0、CPU 1、CPU 2、CPU 3,为1的位表示对应的 CPU 核心会被 `irqbalance` 考虑用于中断分配,为 0 则表示不考虑\r\n\r\n```shell\r\nsudo systemctl restart irqbalance\r\n# 观察中断处理是否符合预期\r\nwatch -n 1 \"cat /proc/interrupts\"\r\n```\r\n\r\n/sys/devices/virtual/workqueue/cpumask,转发线程在用户态运行,若调度到workqueue线程,则会陷入内核态,造成线程损失,因此需要对转发线程所在的核进行workqueue屏蔽\r\n\r\n```shell\r\ncat /sys/devices/virtual/workqueue/cpumask\r\n# f 输出为f,二进制1111,代表所有核都参与\r\n```\r\n\r\n/proc/sys/vm/\r\n\r\n# 5. 附录\r\n## 5.1. 多线程测试应用\r\n```c\r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\n#define NUM_THREADS 4\r\n#define CPU_START 2\r\n#define CPU_END 3\r\n\r\nvoid* worker(void* arg) {\r\n long tid = (long)arg;\r\n \r\n // 设置CPU亲和性,设置不同线程的不同CPU亲和性\r\n cpu_set_t cpuset;\r\n CPU_ZERO(&cpuset);\r\n CPU_SET(CPU_START + (tid % (CPU_END - CPU_START + 1)), &cpuset);\r\n int rc = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);\r\n if (rc != 0) {\r\n perror(\"Error setting thread affinity\");\r\n }\r\n \r\n printf(\"Worker %ld started on CPU %d\\n\", tid, sched_getcpu());\r\n \r\n // 持续消耗CPU的计算任务\r\n while(1) {\r\n double x = 3.14159 * 123456.789;\r\n x /= 3.14159;\r\n x *= 123456.789;\r\n }\r\n return NULL;\r\n}\r\n\r\nint main() {\r\n // 设置主线程亲和性\r\n cpu_set_t cpuset;\r\n CPU_ZERO(&cpuset);\r\n for(int cpu = CPU_START; cpu <= CPU_END; cpu++) {\r\n CPU_SET(cpu, &cpuset);\r\n }\r\n pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);\r\n\r\n pthread_t threads[NUM_THREADS];\r\n \r\n for(long i = 0; i < NUM_THREADS; i++) {\r\n pthread_create(&threads[i], NULL, worker, (void*)i);\r\n }\r\n\r\n // 主线程等待所有工作线程\r\n for(int i = 0; i < NUM_THREADS; i++) {\r\n pthread_join(threads[i], NULL);\r\n }\r\n return 0;\r\n}\r\n```\r\n编译\r\n```shell\r\ngcc -D_GNU_SOURCE cpu_test.c -o cpu_test -pthread\r\n```\r\n## 5.2. grub\r\nGRUB用于多操作系统启动管理的工具,主要配置文件是`/boot/grub/grub.cfg`,但一般不建议直接编辑该文件。GRUB2采用动态生成配置文件的方式,用户可以通过修改 `/etc/default/grub`和`/etc/grub.d`目录下的脚本文件来间接配置启动选项。系统会根据这些设置自动生成最终的`grub.cfg`文件。\r\n- **/boot/grub2/grub.cfg**:常见于基于Red Hat系列的发行版,如Red Hat Enterprise Linux(RHEL)、CentOS、Fedora 等。这些发行版为了明确区分GRUB和 GRUB2,采用`/boot/grub2`目录来存放 GRUB2 的相关文件,`grub.cfg` 就是其中关键的配置文件,它记录了系统的启动选项、内核信息等。\r\n- **/boot/grub/grub.cfg**:多见于基于 Debian 系列的发行版,如 Debian、Ubuntu 等。这些发行版在采用 GRUB2 作为启动加载器后,仍然沿用了`/boot/grub`目录结构,所以 GRUB2 的配置文件存放在该目录下的 `grub.cfg` 中。"},{"id":"numa架构","title":"Numa架构","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":9,"slug":"numa架构","description":"是一种多处理器架构,它将系统中的内存分成多个节点,并将每个节点分配给不同的处理器。在 NUMA 架构中,每个处理器可以访问本地节点的内存,但访问远程节点的内存速度较慢。因此,NUMA 架构可以提高多处理器系统的性能和可扩展性。 下图为英特尔S2600系列服务器主板 - 两个CPU插槽,CPU插槽之间...","relativePath":"Tech/Os/numa架构.md","rawContent":"`NUMA(Non-Uniform Memory Access,非一致性内存访问)`是一种多处理器架构,它将系统中的内存分成多个节点,并将每个节点分配给不同的处理器。在 NUMA 架构中,每个处理器可以访问本地节点的内存,但访问远程节点的内存速度较慢。因此,NUMA 架构可以提高多处理器系统的性能和可扩展性。\r\n\r\n下图为英特尔S2600系列服务器主板\r\n- 两个CPU插槽,CPU插槽之间有明确的物理间隔,形成两个独立的NUMA节点\r\n- 每个CPU插槽上下两侧各2个内存插槽,每个NUMA四个个内存插槽,共计8个插槽\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250226232655713.png)\r\n通过`lscpu`命令可以查看cpu核心的numa分布\r\n```shell\r\n# 2. #lscpu\r\nArchitecture: x86_64\r\nCPU op-mode(s): 32-bit, 64-bit\r\nByte Order: Little Endian\r\nAddress sizes: 46 bits physical, 48 bits virtual\r\nCPU(s): 104\r\nOn-line CPU(s) list: 0-103\r\nThread(s) per core: 2\r\nCore(s) per socket: 26\r\nSocket(s): 2\r\nNUMA node(s): 2\r\nVendor ID: GenuineIntel\r\nCPU family: 6\r\nModel: 85\r\nModel name: Intel(R) Xeon(R) Gold 6278C CPU @ 2.60GHz\r\nStepping: 7\r\nCPU MHz: 3299.567\r\nCPU max MHz: 3500.0000\r\nCPU min MHz: 1000.0000\r\nBogoMIPS: 5200.00\r\nVirtualization: VT-x\r\nL1d cache: 1.6 MiB\r\nL1i cache: 1.6 MiB\r\nL2 cache: 52 MiB\r\nL3 cache: 71.5 MiB\r\nNUMA node0 CPU(s): 0-25,52-77\r\nNUMA node1 CPU(s): 26-51,78-103\r\n```\r\n- **L1d cache**(L1 数据缓存):L1数据缓存是最接近CPU核心(物理核)的缓存,存储最近使用的数据。每个CPU核心通常都有独立的 L1 数据缓存。\r\n- **L1i cache**(L1 指令缓存):L1指令缓存与L1数据缓存类似,但它存储的是指令。每个CPU核心(物理核)通常也有独立的L1指令缓存。\r\n- **L2 cache**(L2 缓存):L2缓存位于L1缓存之后,容量更大,速度略慢。通常,多个 CPU核心共享一个L2缓存。\r\n- **L3 cache**(L3 缓存):L3 缓存是所有核心共享的缓存,它的容量更大,但访问速度相对较慢。L3 缓存用于存储更少使用但仍然频繁访问的数据。L3缓存通常是跨NUMA节点共享的\r\n# 1. 背景\r\n现在的计算机系统通常包含多个CPU和多个内存块。以前,我们通常将所有内存看作是一块共享内存,所有处理器对共享内存的访问方式都是相同的,这就是之前普遍使用的SMP模型。但是随着处理器数量的增加,共享内存可能会导致内存访问冲突变得更加严重,并且当内存访问达到瓶颈时,性能将无法继续提高。为了解决这个问题,引入了NUMA(非一致性内存访问)模型。\r\n\r\n在NUMA模型中,系统内存被分成多个节点,每个节点被分配给不同的处理器。例如,如果一个系统有2个处理器和8个内存模块,我们将其中一个处理器和四个内存块组合成一个NUMA节点,这样系统就有两个NUMA节点。在物理分布上,同NUAM节点内处理器和内存模块的物理距离更近,因此访问速度更快。\r\n\r\n# 2. NUMA节点的物理内存地址规划\r\n在NUMA系统中,物理内存的分布通常是由操作系统管理的。每个NUMA节点的物理内存地址范围通常是**独立**的。例如,如果系统有2个NUMA 节点,每个节点的内存会被分配到不同的物理地址区域,并且通过页表映射到虚拟地址空间。例如,第一个 NUMA 节点的内存地址范围可能是 0x00000000 到 0x10000000,第二个 NUMA 节点的地址范围可能是 0x10000000 到 0x20000000\r\n# 3. 内存亲和性(Memory Affinity)\r\n内存亲和性决定了进程访问本地节点还是远程节点的内存。操作系统会尽量让进程的内存访问本地节点,从而降低访问延迟,提高性能。一般来说,当一个进程被调度到某个节点上时,系统倾向于将该进程的内存分配到同一NUMA节点的内存上,从而减少远程访问(跨节点内存访问)的延迟和带宽瓶颈。操作系统通过策略(如 `NUMA policy`)来决定进程的内存分配方式。例如,Linux 使用 `numactl` 等机制来指定进程在某些节点上分配内存,或者使进程使用本地节点的内存。\r\n# 4. 内存访问\r\n在 NUMA 架构下,物理内存被划分为多个 NUMA 节点(通常是 CPU 插槽或 CPU 内核组)。每个NUMA节点都有自己的本地内存,NUMA节点内的内存访问速度较快,访问其他节点的内存则可能会产生更高的延迟,称为“远程内存访问”。\r\n- **本地内存(Local Memory)**:每个NUMA节点的物理内存与该节点的CPU直接连接,进程如果运行在本地CPU上,访问本地内存速度最快。\r\n- **远程内存(Remote Memory)**:当一个进程在一个节点上运行,但它需要访问另一个节点的内存时,这种访问被称为远程内存访问,通常会比本地内存访问更慢。\r\n```shell\r\n# numastat\r\n node0 node1\r\nnuma_hit 1793608274381 865106746276\r\nnuma_miss 1902135 1637176533\r\nnuma_foreign 1637176533 1902135\r\ninterleave_hit 19086 19313\r\nlocal_node 1793595789836 432231689\r\nother_node 14386680 866311691120\r\n```\r\n- **numa_hit**表示进程在本地NUMA节点(即当前 CPU 所在节点)访问本地内存的次数。这个数值越大,表示该节点的 CPU 更多地在访问本地内存,说明该节点的内存访问效率较高。\r\n- **numa_miss**表示进程在本地NUMA节点(当前 CPU 所在节点)访问其他NUMA节点内存的次数,通常会带来较高的延迟。该值越大,说明该节点的 CPU 更多地在访问远程内存,性能上会受到影响,因为远程访问比本地访问更慢。\r\n- **numa_foreign**表示远程NUMA节点访问本地内存的次数,通常是与**numa_miss**对称的。即,**numa_foreign** 是另一个节点的CPU访问当前节点的内存。\r\n- **interleave_hit**表示交错内存模式下的命中次数。在NUMA系统中,交错内存(Interleave)模式是指内存页面在多个节点间交替分布,以平衡负载。这个统计数值显示了在交错模式下访问内存时的命中次数。这个值的高低表明交错模式的内存访问效果,通常希望此值尽可能大。\r\n# 5. numa内存距离\r\n```shell\r\n# numactl -H\r\navailable: 2 nodes (0-1)\r\nnode 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77\r\nnode 0 size: 191697 MB\r\nnode 0 free: 36826 MB\r\nnode 1 cpus: 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103\r\nnode 1 size: 192972 MB\r\nnode 1 free: 43222 MB\r\nnode distances:\r\nnode 0 1 \r\n 0: 10 21 \r\n 1: 21 10 \r\n```\r\n- `available: 2 nodes (0-1)`表示系统中有2个NUMA节点,编号分别为0和1,表示 NUMA 处于开启状态。若返回信息中 NUMA 节点数为1,即`available: 1 nodes (0)`,则 NUMA 处于关闭状态。\r\n- `node 0 cpus`和`node 1 cpus`列出了每个节点上的CPU列表。每个CPU的编号表示该CPU在系统中的实际编号,CPU列表中的奇数编号表示该CPU所在的核心,偶数编号表示该核心上的线程。\r\n- `node 0 size`和`node 1 size`列出了每个节点的内存总大小,单位为MB。\r\n- `node 0 free`和`node 1 free`列出了每个节点的可用内存大小,单位为MB。\r\n- `node distances`列出了节点之间的距离矩阵。例如,第一行表示节点0到节点0的距离为10,节点0到节点1的距离为21;第二行表示节点1到节点0的距离为21,节点1到节点1的距离为10。距离矩阵的值越小,表示两个节点之间的距离越近。"},{"id":"Os","title":"Os","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"os","description":"","relativePath":"Tech/Os/Os.md","rawContent":""},{"id":"Page cache","title":"Page Cache","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"page-cache","description":"文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被CPU访问。由于读写硬盘的速度比读写内存要慢很多,所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux内核使用页缓存(Page Cache)机制来对文件中的数据进...","relativePath":"Tech/Os/Page cache.md","rawContent":"文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被CPU访问。由于读写硬盘的速度比读写内存要慢很多,所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux内核使用页缓存(Page Cache)机制来对文件中的数据进行缓存。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202410131612406.png)\r\n\r\n\r\n当用户对文件进行读写时,实际上是对文件的页缓存进行读写。所以对文件进行读写操作时,会分以下两种情况进行处理:\r\n- 当从文件中读取数据时,如果要读取的文件偏移量所对应的页缓存是否存在,如果存在就把页缓存中的数据拷贝到应用程序的内存中。否则,内核首先会申请一个空闲的内存页(页缓存),然后从文件中读取数据到页缓存,并且把页缓存的数据拷贝给用户。\r\n- 当向文件中写入数据时,如果要写入的文件偏移量所对应的页缓存是否存在,如果存在那么直接把新数据写入到页缓存即可。否则,内核首先会申请一个空闲的内存页(页缓存),然后从文件中读取数据到页缓存,并且把新数据写入到页缓存中。对于被修改的页缓存,内核会定时把这些页缓存刷新到文件中。\r\n\r\nPage Cache 是由内核管理的内存,位于 VFS(Virtual File System) 层和具体文件系统层(例如 ext4,ext3)之间。应用进程使用 read/write 等文件操作,通过系统调用进入到VFS层,根据O_DIRECT标志,可以使用Page Cache作为文件内容的缓存,也可以跳过Page Cache不使用内核提供的缓存能力。\r\n\r\n另外,应用程序可以使用mmap ,将文件内容映射到进程的虚拟地址空间,可以像读写内存一样直接读写硬盘上的文件。进程的虚拟内存直接和Page Cache映射。通过mmap,减少了数据从磁盘到用户空间的复制次数,提高了文件读写的效率。传统的文件读写需要将数据从磁盘读取到内核缓冲区,再从内核缓冲区复制到用户空间。而使用 mmap 后,数据可以直接在磁盘和用户空间之间进行传输,避免了中间的内核缓冲区复制过程。\r\n\r\n# 1. 参考链接\r\n[一文看懂 | 什么是页缓存(Page Cache)-腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1848933)"},{"id":"中断","title":"中断","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":6,"slug":"中断","description":"1. 中断(Interrupt)的定义 中断是计算机系统中一种关键机制,允许处理器暂停当前执行的任务,转去处理更高优先级的紧急事件,处理完成后恢复原任务。中断机制提高了系统的响应速度和资源利用率,是操作系统实现多任务、实时响应的基础。 2. 硬件中断(Hardware Interrupt) vs 软...","relativePath":"Tech/Os/中断.md","rawContent":"# 1. 中断(Interrupt)的定义\r\n**中断**是计算机系统中一种关键机制,允许处理器**暂停当前执行的任务**,转去处理更高优先级的紧急事件,处理完成后**恢复原任务**。中断机制提高了系统的响应速度和资源利用率,是操作系统实现多任务、实时响应的基础。\r\n# 2. 硬件中断(Hardware Interrupt) vs 软件中断(Software Interrupt)\r\n\r\n| **特征** | **硬件中断** | **软件中断** |\r\n| ------------ | -------------------------------------------------- | ---------------------------------------- |\r\n| **触发源** | 外部硬件设备(如键盘、网卡、定时器) | CPU执行特定指令(如`int 0x80`)或程序异常(如除零错误) |\r\n| **同步性** | **异步**:随机发生,与CPU当前指令无关 | **同步**:由正在执行的程序主动触发 |\r\n| **目的** | 通知CPU外部事件(如数据到达、设备就绪) | 请求系统服务(如系统调用)或处理程序错误 |\r\n| **中断号(IRQ)** | 由硬件分配固定IRQ(如IRQ1对应键盘) | 由软件指定(如Linux系统调用使用`0x80`中断号) |\r\n| **处理流程** | 1. 设备发送中断信号到中断控制器(如APIC)
2. CPU保存现场,跳转到中断处理程序 | 1. 执行`int`指令或触发异常
2. CPU直接查表跳转到处理程序 |\r\n| **典型场景** | 网卡收到数据包、硬盘I/O完成、键盘输入 | 应用程序调用系统API(如读写文件)、除零错误 |\r\n| **优先级** | 可配置优先级(如时钟中断 > 磁盘中断) | 通常由操作系统定义(如异常 > 普通系统调用) |\r\n| **屏蔽能力** | 可通过屏蔽中断(CLI指令)临时禁用 | 不可屏蔽(如某些异常必须处理) |\r\n\r\n# 3. 硬件中断详解\r\n1. **触发过程**:\r\n - 设备通过物理信号线(如INTR、NMI)或消息信号(如MSI-X)通知中断控制器。\r\n - 中断控制器仲裁优先级后,向CPU发送中断请求。\r\n2. **处理步骤**:\r\n - CPU完成当前指令后,保存现场(寄存器、程序计数器)。\r\n - 根据中断号查询**中断向量表**,跳转到对应的**中断服务程序(ISR)**。\r\n - ISR处理完成后,通过`IRET`指令恢复现场。\r\n3. **例子**:\r\n - **时钟中断**:由定时器周期性触发,用于任务调度。\r\n - **网卡中断**:数据包到达时触发,通知CPU读取数据。\r\n# 4. 软件中断详解\r\n1. **触发方式**:\r\n - **主动调用**:程序执行`int n`指令(如Linux系统调用`int 0x80`)。\r\n - **异常触发**:CPU检测到错误(如缺页、除零),自动生成中断。\r\n2. **处理步骤**:\r\n - CPU直接根据中断号跳转到预设的处理程序(如系统调用处理函数或异常处理程序)。\r\n - 处理完成后可能终止程序(如段错误)或恢复执行(如缺页处理)。\r\n3. **例子**:\r\n - **系统调用**:用户程序通过`int 0x80`进入内核态执行特权操作。\r\n - **断点调试**:调试器利用`int 3`指令触发中断以暂停程序。\r\n# 5. 关键区别总结\r\n1. **触发源**:\r\n - 硬件中断来自外部设备,软件中断来自CPU内部(指令或异常)。\r\n2. **同步性**:\r\n - 硬件中断是异步事件,软件中断是同步执行的必然结果。\r\n3. **用途**:\r\n - 硬件中断处理外部通信,软件中断实现系统服务或错误处理。\r\n4. **响应延迟**:\r\n - 硬件中断可能被屏蔽或延迟处理,软件中断通常立即响应。\r\n# 6. 实际应用中的协同\r\n- **操作系统内核**:混合使用两者。例如,网卡收到数据时触发硬件中断,内核处理数据后通过软件中断通知用户程序。\r\n- **性能权衡**:高频硬件中断可能引入开销,因此现代网卡支持**轮询模式**或**中断合并**(如NAPI)。"},{"id":"操作系统地址空间","title":"操作系统地址空间","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":26,"slug":"操作系统地址空间","description":"操作系统的地址空间是一个抽象概念,表示操作系统在运行时能够管理和访问的内存范围。它提供了一种将物理内存映射到逻辑地址的机制,使得程序能够在一个独立于物理硬件的虚拟地址空间中执行。 1. 用户空间与内核空间 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所...","relativePath":"Tech/Os/操作系统地址空间.md","rawContent":"操作系统的地址空间是一个抽象概念,表示操作系统在运行时能够管理和访问的内存范围。它提供了一种将物理内存映射到逻辑地址的机制,使得程序能够在一个独立于物理硬件的虚拟地址空间中执行。\r\n# 1. 用户空间与内核空间\r\n操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将地址空间划分为两部分,一部分为内核空间,一部分为用户空间。\r\n\r\n对于32位操作系统而言,寻址空间(虚拟存储空间)为4G(2的32次方),linux系统将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。\r\n\r\n对于64位操作系统而言,寻址空间为256TB(2的64次方),用户空间(应用程序)的虚拟地址范围为:从0x0000000000000000到0x00007fffffffffff,共128TB。内核空间的虚拟地址范围为:从0xffff800000000000到0xffffffffffffffff,共128TB。实际可用的物理内存规模也远小于该数字,但具备了极大扩展能力。64位系统通过将内核空间和用户空间的虚拟地址隔离到高低非连续区域,实现了安全的内存隔离。同时也提供了超大的寻址容量来支撑未来需求。\r\n## 1.1. 内核空间\r\n- 内核空间是操作系统专用的内存区域,包含操作系统的内核代码、数据结构和驱动程序等。\r\n- 在内核空间运行的代码拥有最高的特权级别,可以执行特权指令,直接访问硬件资源。\r\n- 用户程序无法直接访问内核空间,需要通过系统调用请求内核执行一些特权操作。\r\n## 1.2. 用户空间\r\n- 用户空间是为用户进程分配的内存区域,包含用户程序的代码、数据、堆和栈等。\r\n- 用户程序在用户空间内执行,具有较低的特权级别。\r\n- 用户程序通过系统调用向内核发出请求,以便执行一些需要特权级别的操作。\r\n## 1.3. 进程切换\r\n为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。\r\n\r\n从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:\r\n1. 保存处理机上下文,包括程序计数器和其他寄存器。\r\n2. 更新PCB信息。\r\n3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。\r\n4. 选择另一个进程执行,并更新其PCB。\r\n5. 更新内存管理的数据结构。\r\n6. 恢复处理机上下文。\r\n### 1.3.1. PCB\r\nPCB(Process Control Block,进程控制块)是操作系统中用于管理和维护进程信息的数据结构。每个正在运行的进程都有一个对应的 PCB,其中存储了该进程的各种状态、上下文信息以及控制信息。PCB 通常由操作系统内核维护和管理,包含的主要信息有:\r\n1. **进程状态(Process State):**\r\n - 存储进程的当前状态,如运行(Running)、就绪(Ready)、阻塞(Blocked)等。\r\n - 状态的变化由操作系统内核根据进程的执行情况进行管理。\r\n2. **程序计数器(Program Counter):**\r\n - 记录了进程下一条要执行的指令的地址。\r\n - 在进程切换时,操作系统保存和恢复程序计数器的值。\r\n3. **寄存器集合(Register Set):**\r\n - 包括通用寄存器、程序状态寄存器等。\r\n - 保存了进程在执行过程中的各种寄存器的当前值。\r\n4. **进程标识符(Process ID):**\r\n - 唯一标识一个进程的标识符。\r\n - 操作系统使用进程标识符来管理和识别进程。\r\n5. **优先级(Priority):**\r\n - 进程的优先级信息,用于调度算法中的进程调度决策。\r\n6. **进程调度信息:**\r\n - 包括进程的调度状态、等待时间、运行时间等。\r\n - 用于操作系统进行进程调度和资源分配。\r\n7. **进程控制信息:**\r\n - 包括进程所拥有的资源、权限等控制信息。\r\n - 用于管理进程对系统资源的访问。\r\n8. **内存管理信息:**\r\n - 包括进程的内存分配情况,页表信息等。\r\n - 用于操作系统进行内存管理。\r\n9. **文件描述符表(File Descriptor Table):**\r\n - 记录了进程打开的文件及其属性。\r\n - 用于文件的读写和管理。\r\n10. **进程间通信信息:**\r\n - 记录了进程与其他进程之间进行通信的相关信息,如消息队列、共享内存等。\r\n11. **信号和处理器状态:**\r\n - 记录了进程当前对信号的处理方式和相关状态。\r\n - 用于处理进程收到的信号。\r\nPCB 提供了操作系统对进程的抽象和控制,允许操作系统在多任务环境中有效地管理和调度进程。当操作系统进行进程切换时,会保存当前运行进程的状态到其对应的 PCB 中,然后加载下一个要执行的进程的 PCB,从而实现进程的无缝切换。\r\n# 2. 分页和分页\r\n## 2.1. 分段(Segmentation):\r\n- 采用分段机制将地址空间划分为不同的段,如代码段、数据段、堆、栈等。\r\n- 每个段都有不同的权限和属性,以实现对程序和数据的不同保护和访问控制。\r\n## 2.2. 分页(Paging):\r\n- 采用分页机制将地址空间划分为固定大小的页面,通常为4KB。\r\n- 操作系统通过页表来映射虚拟地址到物理地址,实现虚拟内存管理。\r\n\r\n> [DPDK大页内存原理_Linux_赖猫_InfoQ写作社区](https://xie.infoq.cn/article/a7c83189a19387018b8595e98)\r\n# 3. 栈和堆\r\n## 3.1. 栈(Stack):\r\n- 栈是一种后进先出(LIFO)的数据结构,用于存储函数调用时的局部变量、返回地址等。\r\n- 栈空间由操作系统自动管理,通过栈指针进行操作。\r\n## 3.2. 堆(Heap):\r\n- 堆是一块用于动态分配内存的区域,由程序员手动管理。\r\n- 通过堆管理函数(如`malloc`、`free`)进行内存的分配和释放\r\n# 4. 共享内存和内存映射\r\n## 4.1. 共享内存:\r\n- 用于实现进程间通信,允许多个进程共享同一块物理内存。\r\n- 进程通过共享内存区域直接读写数据,避免了复制数据的开销。\r\n## 4.2. 内存映射:\r\n- 允许文件或设备映射到进程的地址空间,使得对文件的读写可以通过内存访问来完成。\r\n- 通过`mmap`等系统调用实现内存映射。\r\n# 5. 文件描述符(File Descriptor)\r\nlinux系统中一切都可以看成文件,文件分为:普通文件、目录文件、链接文件、字符设备文件、块设备文件和套接口文件。分别通过字符-/d/l/c/b/s指代。\r\n文件描述符是内核为了高效管理**已经被打开的文件所创建的索引**。其值通常为一个非负整数,用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。\r\n- 每个文件描述符会与一个打开的文件相对应\r\n- 不同文件描述符也可能指向同一个文件\r\n- 相同的文件可以被不同的进程打开,也可以在同一个进程中被打开多次\r\nlinux提供了三个表来维护文件描述符,分别是:进程级的文件描述符表,系统及的文件描述符表,文件系统的i-node表\r\n![linux系统文件描述符](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/linux%E5%9F%BA%E7%A1%80/linux%E7%B3%BB%E7%BB%9F%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E8%A1%A8.png)\r\n\r\n- 在进程A中,文件描述符1和30都指向了同一个打开的文件句柄(#23),这可能是该进程多次对执行**打开**操作\r\n- 进程A中的文件描述符2和进程B的文件描述符2都指向了同一个打开的文件句柄(#73),这种情况有几种可能\r\n\t- 进程A和进程B可能是父子进程关系\r\n\t- 进程A和进程B打开了同一个文件,且文件描述符相同(低概率事件)\r\n\t- A、B中某个进程通过UNIX域套接字将一个打开的文件描述符传递给另一个进程。\r\n\t- 进程A的描述符0和进程B的描述符3分别指向不同的打开文件句柄,但这些句柄均指向i-node表的相同条目(#1936),换言之,指向同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了打开请求。同一个进程两次打开同一个文件,也会发生类似情况。\r\n\r\n### 5.1.1. 缓存I/O\r\n缓存I/O又被称作标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,数据先从磁盘复制到内核空间的缓冲区,然后从内核空间缓冲区复制到应用程序的地址空间。\r\n**读操作**:操作系统检查内核的缓冲区有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回;否则从磁盘中读取,然后缓存在操作系统的缓存中。\r\n**写操作**:将数据从用户空间复制到内核空间的缓存中。这时对用户程序来说写操作就已经完成,至于什么时候再写到磁盘中由操作系统决定,除非显示地调用了sync同步命令\r\n### 5.1.2. 阻塞与同步\r\n阻塞、非阻塞说的是调用者。同步、异步说的是被调用者。调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。\r\n#### 5.1.2.1. 同步与异步\r\n**同步请求**\r\nA调用B,B的处理是同步的,在处理完之前他不会通知A,只有处理完之后才会明确的通知A。\r\n**异步请求**\r\nA调用B,B的处理是异步的,B在接到请求后先告诉A我已经接到请求了,然后异步去处理,处理完之后通过回调等方式再通知A。\r\n> 同步和异步最大的区别就是被调用方的执行方式和返回时机。 同步指的是被调用方做完事情之后再返回,异步指的是被调用方先返回,然后再做事情,做完之后再想办法通知调用方。\r\n#### 5.1.2.2. 阻塞与非阻塞\r\n**阻塞请求**\r\nA调用B,A一直等着B的返回,别的事情什么也不干。\r\n**非阻塞请求**\r\nA调用B,A不用一直等着B的返回,先去忙别的事情了。\r\n\r\n所以说,阻塞和非阻塞最大的区别就是在被调用方返回结果之前的这段时间内,调用方是否一直等待。 阻塞指的是调用方一直等待别的事情什么都不做。非阻塞指的是调用方先去忙别的事情。\r\n### 5.1.3. Unix中的五种I/O模型\r\n对于一次I/O访问read操作分为两个阶段\r\n1. 等待数据准备,数据被拷贝到操作系统内核的缓冲区\r\n2. 数据从内核缓冲区拷贝到用户态缓冲区\r\n对于socket流而言\r\n1. 通常涉及到等待网络上的数据分组到达,也就是被复制到内核的某个缓冲区\r\n2. 把数据从内核缓冲区复制到应用进程缓冲区\r\n#### 5.1.3.1. 同步阻塞I/O\r\n分为两个阶段,这两个阶段都必须完成后才能继续下一步操作,blocking IO的特点就是IO执行的两个阶段都被block了。\r\n1. 等待数据就绪。网络I/O中就是等待远端数据陆续抵达。数据从网络中或者从磁盘上被复制到内核缓冲区中。\r\n2. 数据拷贝。出于系统安全考虑,用户态的程序没有权限直接读取内核态内存,因此内核负责把内核态内存中的数据拷贝一份到用户态内存中\r\n\r\n![blockingIO](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/linux%E5%9F%BA%E7%A1%80/blocking_io.png)\r\n\r\n> send函数是应用程序用来向TCP连接的另一端发送数据\r\n> recvfrom或recv函数是应用程序用来从TCP连接的另一端接收数据\r\n#### 5.1.3.2. 同步非阻塞I/O\r\n非阻塞是对于主调方来说的,用户进程可以在阶段1的时候选择去做其他事情,通过轮询的方式看看内核缓冲区是否就绪。如果数据就绪,再执行阶段2,第2阶段的拷贝数据的整个过程,进程仍然是属于阻塞状态的。nonblocking IO的特点就是用户进程需要不断的主动轮询kernel数据好了没有。\r\n\r\n \r\n\r\n![noneBlockingIO](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/linux%E5%9F%BA%E7%A1%80/noblocking_io.png)\r\n#### 5.1.3.3. I/O多路复用\r\nI/O多路复用也称为时间驱动模型。I/O多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。目前支持I/O多路复用的系统调用有select、pselect、poll、epoll,一个进程可以监听多个描述符,一旦某个文件描述符fd就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。select/pselect/poll/epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。相比如同步非阻塞I/O,它的改进在于原本需要用户进程去轮询的事情交给了内核线程帮你完成,而且这个内核线程可以等待多个socket,能实现同时对多个I/O端口进行监听。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用多线程 + 阻塞IO的web server性能更好,可能延迟还更大。 也就是说,select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。高并发的程序一般使用同步非阻塞方式而非多线程 + 同步阻塞方式。在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如下图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。\r\n- select: select可以先对要操作的描述文件符进行查询,查看是否目标描述符可以进行读、写或者错误操作,然后当文件描述符满足操作条件的时候才进行真正的I/O操作。函数select()返回值为0,-1或者一个大于1的整数值,当监视的文件集中有文件描述符符合要求,即读文件描述符集中的文件可读,写文件描述符集中的文件可写或者错误文件描述符中的文件发生错误时,返回值为大于0的正值;当超时的时候返回0;当发生错误的时候返回-1。\r\n- pselect: 与select函数一致,除了超时时间结构是纳秒级的结构。不过Linux平台下内核调度的精度为10毫秒级,所以根本达不到设置的精度。\r\n- poll: poll解决了select中fds集合大小1024的限制。但是,它并没改变大量描述符数组被整体复制于用户态和内核态的地址空间之间,以及个别描述符就绪触发整体描述符集合的遍历的低效问题。poll随着监控的socket集合的增加性能线性下降,poll不适合用于大并发场景。\r\n- epoll: 是select和poll的增强版。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。\r\n\r\n![io多路复用](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/linux%E5%9F%BA%E7%A1%80/io%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8.png)\r\n\r\n \r\n\r\n### 5.1.4. 信号驱动I/O\r\n信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。信号驱动式I/O对于TCP套接字近乎无用,因为该信号产生得过于频繁,不能区分具体是哪种事件\r\n- 监听套接字上某个连接请求请求已经完成\r\n- 某个断连接请求已经发起\r\n- 某个连接之半已经关闭\r\n- 数据到达套接字\r\n- 数据已经从套接字发送走(即输出缓冲区有空闲空间)\r\n- 发生某个异步错误\r\n### 5.1.5. 异步I/O\r\n相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段, 进程都是非阻塞的。Linux提供了AIO库函数实现异步,但是用的很少。目前有很多开源的异步IO库,例如libevent、libev、libuv。\r\n\r\n![异步IO](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/linux%E5%9F%BA%E7%A1%80/asynchronousIO.png)\r\n\r\n \r\n\r\n# 6. 参考文献\r\n[Linux IO模式及select、poll、epoll详解]()\r\n[文件描述符简介](https://segmentfault.com/a/1190000009724931)\r\n[Linux中的文件描述符与打开文件之间的关系]()\r\n[缓存IO与直接IO]()\r\n[详解 Java 中 4 种 I/O 模型]()\r\n[Linux网络编程--select()和pselect()函数]()\r\n# 7. select和poll\r\nEpoll是Linux内核提供的一种高效的I/O多路复用机制,可以高效地处理大量socket,提供高并发的网络通信。其高效的原理主要在于以下几点:\r\n1. 基于事件(event)驱动,避免了select/poll需轮询所有FD的模型。\r\n2. 将用户空间FD相关数据内核化,减少了上下文切换开销。\r\n3. 使用红黑树作为FD存储结构,加速查找速度。\r\n4. 采用水平触发,避免了重复通知的效率问题。\r\n5. 支持边缘触发,减少无效中断。\r\n6. 一定程度上实现了\"反应堆\"模式,不直接调用回调函数,进一步提高效率。\r\n\r\n总的来说,epoll通过内核化、锁优化、减少复制以及高效的数据结构访问等手段,极大提升了I/O的并发处理能力,使其可以支撑海量并发连接,这就是epoll的高效设计原理。"},{"id":"操作系统概述","title":"操作系统概述","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":7,"slug":"操作系统概述","description":"1. 操作系统基本特征 1.1. 并发 并发是指两个或多个事件在同一时间间隔内发生,在多道程序环境下,一段时间内宏观上有多个程序在同时执行,而在同一时刻,单处理器环境下实际上只有一个程序在执行,故微观上这些程序还是在分时的交替进行。操作系统的并发是通过分时得以实现的。操作系统的并发性是指计算机系统中...","relativePath":"Tech/Os/操作系统概述.md","rawContent":"# 1. 操作系统基本特征\r\n\r\n## 1.1. 并发\r\n\r\n并发是指两个或多个事件在同一时间间隔内发生,在多道程序环境下,一段时间内宏观上有多个程序在同时执行,而在同一时刻,单处理器环境下实际上只有一个程序在执行,故微观上这些程序还是在分时的交替进行。操作系统的并发是通过分时得以实现的。操作系统的并发性是指计算机系统中同时存在多个运行着的程序,因此它具有同时处理和调度多个程序执行的能力。在操作系统中,引入进程的目的是使程序能并发执行。\r\n\r\n> 并发与并行的区别:并发的关键是你有处理多个任务的能力,不一定要同时(单核cpu分时调度)。并行的关键是你有**同时**处理多个任务的能力。\r\n\r\n## 1.2. 共享\r\n\r\n资源共享即共享,是指系统中的资源可供内存中多个并发执行的进程共同使用。共享可以分为以下两种资源共享方式。\r\n\r\n### 1.2.1. 互斥共享\r\n\r\n系统中的某些资源,如打印机、磁带机,虽然他们可以提供给多个进程使用,但为使所打印的内容不致造成混淆,应规定在同一段时间内只允许一个进程方位该资源。为此,当进程a访问某资源时,必须先提出请求,如果此时该资源空闲,系统便可将之分配给进程a使用,伺候若再有其他进程也要访问该资源(只要a未用完)则必须等待。仅当进程a访问完并释放该资源后,才允许另一进城对该资源进行访问。计算机系统中的物理设备,以及某些软件中所用的栈、变量和表格,都属于临界资源,他们都要求被互斥的共享。\r\n\r\n### 1.2.2. 同时访问\r\n \r\n系统中还有一种资源,允许在一段时间内由多个进程“同时”对它进行访问。这里所谓的“同时”往往是宏观上的,而在微观上,这些进程可能是交替的对该资源进行访问即“分时共享”。典型的可供多个进程同时访问的资源是磁盘设备,一些用重入码编写的文件也可以被“同时”共享,即若干个用户同时访问该文件。\r\n\r\n> 并发和共享是操作系统两个最基本的特征,这两者之间又是互为存在条件的:\r\n> 1. 资源共享是以程序的并发为条件的,若系统不允许程序并发执行,则自然不存在资源共享的问题;\r\n> 2. 若系统不能对资源共享实施有效地管理,也必将影响到程序的并发执行,甚至根本无法并发执行。\r\n\r\n# 2. 虚拟化\r\n\r\n虚拟是指把一个物理上的实体变为若干个逻辑上的对应物。物理实体是实的,即实际存在的;而后者是虚的,是用户感觉上的事物。相应的,用于实现虚拟的技术,成为虚拟技术。在操作系统中利用了多种虚拟技术,分别用来实现虚拟处理器、虚拟内存和虚拟外部设备。\r\n\r\n在虚拟处理器技术中,是通过多道程序设计技术,让多道程序并发执行的方法,来分时使用一台处理器的。此时,虽然只有一台处理器,但他能同时为多个用户服务,是每个终端用户都认为是有一个中央处理器在为他服务。利用多道程序设计技术,把一台物理上的CPU虚拟为多台逻辑上的CPU,称为虚拟处理器。\r\n\r\n类似的,可以通过虚拟存储器技术,将一台机器的物理存储器变为虚拟存储器,一边从逻辑上来扩充存储器的容量。当然, 这是用户所感觉到的内存容量是虚的,称为存储器程序虚拟存储器。\r\n\r\n还可以通过虚拟设备技术,将一台物理IO设备虚拟为多台逻辑上的IO设备,并允许每个用户占用一台逻辑上的IO设备,这样便可使原来仅允许在一段时间内有一个用户访问的设备,变为在一段时间内允许多个用户同时访问的共享设备。\r\n\r\n因此操作系统的虚拟技术可归纳为:时分复用技术和空分复用技术。"},{"id":"进程调度策略","title":"进程调度策略","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"进程调度策略","description":"进程调度的关注点: - 性能:执行完所有任务需要的总时间。 - 公平性:每个任务都期望自己先被执行,尽早完成。 - 响应时间:首次运行时间 - 任务提交时间。 性能、公平性、响应时间是矛盾的,为了达到最好的性能,我们希望cpu不要在不同进程或线程之间切换,这样避免了上下文的切换,一个任务从开始执行到...","relativePath":"Tech/Os/进程/进程调度策略.md","rawContent":"进程调度的关注点:\r\n- 性能:执行完所有任务需要的总时间。\r\n- 公平性:每个任务都期望自己先被执行,尽早完成。\r\n- 响应时间:首次运行时间 - 任务提交时间。\r\n性能、公平性、响应时间是矛盾的,为了达到最好的性能,我们希望cpu不要在不同进程或线程之间切换,这样避免了上下文的切换,一个任务从开始执行到结束,但这样势必会带来不公平,以及很差的响应时间。\r\n\r\nFIFO: 最简单的算法,可以说性能最好,没有任何任务切换\r\nSJF(shortest job first): FIFO有个问题,如果第一个到来的任务需要很长时间,那么后面到来的任务即使执行时间很短,也需要等第一个任务执行完成,这样就会造成所有任务都很久之后才能执行完成。有个简单的方法就是SJF,总是保证短的任务先被执行并完成。\r\nRR(round-robin):采用时间分片的方式,cpu轮询调度每个任务,这样可以带来比较好的响应时间。只需要保证CPU单次调度的执行时间 >> 上下文切换时间即可。比如CPU单次最短执行10毫秒,上下文切换1微秒,那么效率也能达到`10000/(10000 + 1)`"},{"id":"openssl","title":"Openssl","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"openssl","description":"1. RSA密钥 生成私钥 - :指定算法为 RSA - :指定密钥长度为 2048 位(可改为 4096 - :输出私钥文件路径 生成公钥 - :输入私钥 - :导出公钥 - :输出公钥文件路径 2. ECC密钥 生成对应的ECC私钥 - :指定椭圆曲线(可换成 , , , 等) - :生成密钥 ...","relativePath":"Tech/security/openssl.md","rawContent":"# 1. RSA密钥\r\n生成私钥\r\n- `-algorithm RSA`:指定算法为 RSA\r\n- `-pkeyopt rsa_keygen_bits:2048`:指定密钥长度为 2048 位(可改为 4096\r\n- `-out rsa_private.pem`:输出私钥文件路径\r\n```shell\r\nopenssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048\r\n```\r\n生成公钥\r\n- `-in rsa_private.pem`:输入私钥\r\n- `-pubout`:导出公钥\r\n- `-out rsa_public.pem`:输出公钥文件路径\r\n```shell\r\nopenssl rsa -pubout -in private_key.pem -out public_key.pem\r\n```\r\n\r\n# 2. ECC密钥\r\n生成对应的ECC私钥\r\n- `-name prime256v1`:指定椭圆曲线(可换成 `secp256k1`, `secp384r1`, `secp521r1`, `X25519` 等)\r\n- `-genkey`:生成密钥\r\n- `-noout`:不显示额外信息\r\n- `-out ecc_private.pem`:输出ECC私钥路径\r\n```shell\r\nopenssl ecparam -name prime256v1 -genkey -noout -out ecc_private.pem\r\n```\r\n生成对应的ECC公钥\r\n- `-in ecc_private.pem`:输入私钥\r\n- `-pubout`:导出公钥\r\n- `-out ecc_public.pem`:输出公钥路径\r\n```shell\r\nopenssl ec -in ecc_private.pem -pubout -out ecc_public.pem\r\n```\r\n# 3. SM2密钥(国密)\r\nSM2是中国标准的椭圆曲线密码算法,需要 `openssl` 支持国密(如 OpenSSL 1.1.1 以上的版本)。\r\n\r\n生成SM2私钥\r\n```shell\r\nopenssl ecparam -name SM2 -genkey -noout -out sm2-key.pem\r\n```\r\n生成公钥\r\n```shell\r\nopenssl ec -in sm2-key.pem -pubout -out sm2pubkey.pem\r\n```\r\n\r\n# 4. Ed25519\r\n```\r\n# 生成pkcs#8格式私钥\r\nopenssl genpkey -algorithm ED25519 -out ca.key\r\n\r\n```"},{"id":"SSL证书","title":"SSL证书","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":16,"slug":"ssl证书","description":"在密码学和数字证书体系中,一般没有“私钥证书”的说法。主要是公钥证书。公钥证书(通常就简称为数字证书)是一种电子文档,包含以下信息 - 公钥信息 - 持有者身份信息(如姓名、组织等) - 证书颁发机构(CA)的数字签名等 其主要目的是将公钥与特定的实体(如个人、服务器等)绑定,用于在网络通信等场景中...","relativePath":"Tech/security/SSL证书.md","rawContent":"在密码学和数字证书体系中,一般没有“私钥证书”的说法。主要是公钥证书。公钥证书(通常就简称为数字证书)是一种电子文档,包含以下信息\r\n- 公钥信息\r\n- 持有者身份信息(如姓名、组织等)\r\n- 证书颁发机构(CA)的数字签名等\r\n其主要目的是将**公钥与特定的实体(如个人、服务器等)绑定**,用于在网络通信等场景中验证对方身份,确保信息是发送给正确的接收方,并且保证信息传输的保密性、完整性和不可否认性。例如,在网站的SSL/TLS加密通信中,浏览器会验证网站的公钥证书来确保安全连接。\r\n# 1. 证书域名绑定\r\n证书分为范域名证书和单域名证书。\r\n## 1.1. 泛域名证书\r\n泛域名证书可以保护一个主域名以及该主域名下的所有[子域名](https://zhida.zhihu.com/search?content_id=235980729&content_type=Article&match_order=1&q=%E5%AD%90%E5%9F%9F%E5%90%8D&zhida_source=entity)。当使用泛域名证书时,同一个证书可以保护多个同级子域名,这种证书通常使用通配符`*`来表示,如`*.example.com`,因此也被称为通配符SSL证书。\r\n- 泛域名证书只能匹配同级别的子域名,不能跨级匹配。例如,`*.shinerio.site`的域名证书匹配`wiki.shinerio.site`、`learn.shinerio.site`等子域名,但是不匹配`guide.demo.shinerio.site`、`developer.demo.shinerio.site`等域名。\r\n- 泛域名证书一般仅支持申请单个通配符域名的证书,不支持多通配符域名的证书,如`*.*.shinerio.site`\r\n泛域名证书的一个主要优势是,它可以适用于未来增加的子域名,无需为每个新的子域名单独购买和安装SSL证书。另外,泛域名证书还可以**简化证书管理和配置**。\r\n## 1.2. 单域名证书\r\n如其名称所示,单域名证书仅适用于一个域名或子域名。这意味着,如果有多个域名或子域名需要保护,就需要为每个域名或子域名分别购买和安装单域名证书。单域名证书的优点是在特定于某个域名的通信中,提供了**更高的安全性和保护级别**。与泛域名证书相比,单域名证书的价格更为**经济实惠**。\r\n# 2. 证书类型\r\n在 SSL/TLS 证书体系中,DV(Domain Validation)、OV(Organization Validation)和 EV(Extended Validation)是根据**验证级别**和**信任等级**划分的三种类型,主要区别体现在**验证流程严格程度**、**信任标识**、**适用场景**等方面。\r\n\r\n| **维度** | **DV 证书(域名验证)** | **OV 证书(组织验证)** | **EV 证书(扩展验证)** |\r\n| ---------- | --------------------- | --------------------------------- | ---------------------------------------------- |\r\n| **验证内容** | 仅验证域名所有权(DNS / 文件验证等) | 验证域名所有权 + **组织真实性**(工商注册信息、联系方式等) | 验证域名所有权 + **组织真实性** + **严格身份审核**(法律文件、运营地址核实等) |\r\n| 安全等级 | 一般 | 高 | 最高 |\r\n| **验证机构审核** | 自动或简单人工审核(最快几分钟完成) | 人工审核组织信息(需 1-5 个工作日) | 第三方机构深度审核(需 5-10 个工作日或更久) |\r\n| **证书价格** | 最低(几十到几百元 / 年) | 中等(数百到数千元 / 年) | 最高(数千元到数万元 / 年) |\r\n| **证书有效期** | 通常 1-2 年 | 通常 1-3 年 | 通常 1-3 年 |\r\n\r\nDV vs OV\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250604220737.png)\r\n\r\n## 2.1. DV 证书(域名验证)\r\n仅包含**域名信息**(通常在`CN`字段),**无组织信息**(如公司名称、地址等)。 \r\n```plaintext\r\nCN = example.com # 仅域名,无组织名\r\n```\r\nDV证书仅验证域名所有权,无需审核组织资质,因此不包含企业信息。\r\n## 2.2. OV 证书(组织验证)\r\n包含**完整的组织信息**,至少包括:\r\n- `CN`(Common Name):域名或子域名(如`www.example.com`)。\r\n- `O`(Organization):组织名称(如 “北京某科技有限公司”)。\r\n- `C`(Country):国家代码(如 “CN”)。\r\n- 部分证书可能包含`L`(Locality,地区)、`ST`(State,州 / 省)等。 \r\n```plaintext\r\nCN = www.example.com \r\nO = 北京某科技有限公司 \r\nC = CN \r\nST = 北京市 \r\nL = 朝阳区\r\n```\r\nOV 证书需验证组织真实性,因此必须包含工商注册的企业名称及基本信息。\r\n## 2.3. EV 证书(扩展验证)\r\n**组织信息最完整**,除包含 OV 证书的所有字段外,可能额外包含:\r\n- `OU`(Organizational Unit,组织单元,如 “信息技术部”)。\r\n- `STREET`(街道地址,用于物理地址验证)。\r\n**关键区别**:EV 证书的`Subject`字段中的组织名称需与**工商数据库完全一致**,且可能通过`subjectAltName`扩展字段包含更多域名。\r\n如下是中国银行证书的subject部分\r\n```plaintext\r\nCN = www.boc.cn\r\nO = Bank of China Limited\r\nST = Beijing\r\nC = CN\r\nserialNumber = 911000001000013428\r\nbusinessCategory = Private Organization\r\njurisdictionStateOrProvinceName = Beijing\r\njurisdictionCountryName = CN \r\n```\r\n查看issuser也可以看出为EV证书\r\n```plaintext\r\nCN = DigiCert Secure Site Pro EV G2 TLS CN RSA4096 SHA256 2022 CA1\r\nO = DigiCert, Inc.\r\nC = US\r\n```\r\nEV 证书需通过严格的身份审核(如法律文件、运营地址核实),因此组织信息最详细且真实可查。\r\n# 3. 证书内容解析\r\nx.509证书是一种广泛使用的数字证书标准,用于在网络通信中验证实体(如服务器、客户端、用户)的身份,并确保数据传输的安全性和完整性。它是公钥基础设施(PKI,Public Key Infrastructure)的核心组成部分,常见于 HTTPS、VPN、电子邮件加密等场景\r\n如下是一个google公钥证书。\r\n![[www.google.com.pem]]\r\n使用如下命令查看证书内容\r\n```shell\r\nopenssl x509 -in www.google.com.pem -text -noout\r\n```\r\n输出如下\r\n```\r\nCertificate:\r\n Data:\r\n Version: 3 (0x2)\r\n Serial Number:\r\n 43:eb:8c:7c:8d:cc:8f:44:10:64:56:21:46:d3:f0:26\r\n Signature Algorithm: ecdsa-with-SHA256\r\n Issuer: C=US, O=Google Trust Services, CN=WE2\r\n Validity\r\n Not Before: Jan 6 08:38:03 2025 GMT\r\n Not After : Mar 31 08:38:02 2025 GMT\r\n Subject: CN=www.google.com\r\n Subject Public Key Info:\r\n Public Key Algorithm: id-ecPublicKey\r\n Public-Key: (256 bit)\r\n pub: \r\n 04:66:80:6e:cb:6d:2a:dc:81:12:96:39:38:7f:26:\r\n c0:e1:0f:19:f6:67:f6:a8:cf:ae:c1:7b:f6:55:e7:\r\n 92:7e:57:73:fd:24:a1:66:d1:22:ec:d8:ba:d6:3d:\r\n 23:3e:5b:77:03:6c:02:18:66:a4:05:69:9a:95:1f:\r\n 05:c8:8c:01:46\r\n ASN1 OID: prime256v1\r\n NIST CURVE: P-256\r\n X509v3 extensions:\r\n X509v3 Key Usage: critical\r\n Digital Signature\r\n X509v3 Extended Key Usage: \r\n TLS Web Server Authentication\r\n X509v3 Basic Constraints: critical\r\n CA:FALSE\r\n X509v3 Subject Key Identifier: \r\n 87:26:06:BF:75:D5:44:29:C0:08:6F:D9:51:1E:32:28:94:46:26:40\r\n X509v3 Authority Key Identifier: \r\n keyid:75:BE:C4:77:AE:89:F6:44:37:7D:CF:B1:68:1F:1D:1A:EB:DC:34:59\r\n\r\n Authority Information Access: \r\n OCSP - URI:http://o.pki.goog/we2\r\n CA Issuers - URI:http://i.pki.goog/we2.crt\r\n\r\n X509v3 Subject Alternative Name: \r\n DNS:www.google.com\r\n X509v3 Certificate Policies: \r\n Policy: 2.23.140.1.2.1\r\n\r\n X509v3 CRL Distribution Points: \r\n\r\n Full Name:\r\n URI:http://c.pki.goog/we2/dTM3-0hpWfE.crl\r\n\r\n 1.3.6.1.4.1.11129.2.4.2: \r\n ......v.Nu.'\\...8[l..?R.......i...d.b.9.....:........G0E. TR/I....n.(....>i...\\r....)...P..!.......hB....~...N...B4..9./....v.v.....P$|k...V7....+E6.....2u:.AU. Z..j.'....'4@....#(.?.....q...L.?]\r\n Signature Algorithm: ecdsa-with-SHA256\r\n 30:45:02:21:00:ea:41:14:8a:23:05:1f:70:25:d0:36:d5:f4:\r\n 91:f0:9a:40:f3:77:e6:b5:cb:b2:21:ca:ca:8b:f2:b0:6c:a1:\r\n 68:02:20:49:a6:3b:e9:b4:bf:1e:41:aa:05:dd:50:7e:4a:93:\r\n 4e:6d:61:e7:b8:04:d0:55:fc:44:80:a3:67:c4:a2:46:3b\r\n```\r\n## 3.1. 基本信息\r\n- **版本**:X.509 v3(0x2)\r\n- **序列号**:`43:eb:8c:7c:8d:cc:8f:44:10:64:56:21:46:d3:f0:26`(由颁发机构分配的唯一标识符)\r\n- **签名算法**:ecdsa-with-SHA256\r\n## 3.2. 颁发者(Issuer)\r\n- **国家**(C,Country):US(美国)\r\n- **组织**(O,organization):Google Trust Services。CA 所属的组织或公司名称(如 “DigiCert Inc”“Let’s Encrypt”)。\r\n- **通用名称**(CN,Common Name):WE2(Google的Web PKI证书颁发机构)。CA 的完整标识名称,通常是CA的全称或域名(如 “DigiCert Inc”等)。\r\n如下是中行证书的issuer部分\r\n```plaintext\r\nCN = DigiCert Secure Site Pro EV G2 TLS CN RSA4096 SHA256 2022 CA1\r\nO = DigiCert, Inc.\r\nC = US\r\n```\r\n## 3.3. 有效期(Validity)\r\n- **生效日期**:2025年1月6日 08:38:03 GMT\r\n- **过期日期**:2025年3月31日 08:38:02 GMT\r\n## 3.4. 主体(Subject)\r\n- **通用名称**(CN,common name):www.google.com(证书所属网站)\r\n## 3.5. 公钥信息\r\n- **算法**:id-ecPublicKey(椭圆曲线公钥)\r\n- **密钥长度**:256 位\r\n- **公钥值**:04:66:80:6e:cb:6d:2a:dc...(略)\r\n- **曲线类型(ASN1 OID)**:prime256v1(也称为NIST P-256)\r\n## 3.6. X.509 v3扩展\r\n- **密钥用途**:Digital Signature(数字签名)\r\n- **扩展密钥用途**:TLS Web Server Authentication(TLS网站服务器认证)\r\n- **基本约束**:CA:FALSE(表明这不是CA证书,不能用于签发其他证书)\r\n- **主体密钥标识符**:87:26:06:BF...(略)\r\n- **授权密钥标识符**:75:BE:C4:77...(略)(用于识别签发证书的CA的公钥)\r\n### 3.6.1. 权威信息访问(Authority Information Access)\r\n- **OCSP(Online Certificate Status Protocol)**:[http://o.pki.goog/we2(在线证书状态协议,用于检查证书是否被吊销)](http://o.pki.goog/we2%EF%BC%88%E5%9C%A8%E7%BA%BF%E8%AF%81%E4%B9%A6%E7%8A%B6%E6%80%81%E5%8D%8F%E8%AE%AE%EF%BC%8C%E7%94%A8%E4%BA%8E%E6%A3%80%E6%9F%A5%E8%AF%81%E4%B9%A6%E6%98%AF%E5%90%A6%E8%A2%AB%E5%90%8A%E9%94%80%EF%BC%89)\r\n- **CA签发者(CA Issuers)**:[http://i.pki.goog/we2.crt(CA证书下载地址)](http://i.pki.goog/we2.crt%EF%BC%88CA%E8%AF%81%E4%B9%A6%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80%EF%BC%89)\r\n### 3.6.2. 主体备用名称\r\n- **DNS**:[www.google.com(证书保护的域名)](http://www.google.com%EF%BC%88%E8%AF%81%E4%B9%A6%E4%BF%9D%E6%8A%A4%E7%9A%84%E5%9F%9F%E5%90%8D%EF%BC%89)\r\n### 3.6.3. 证书策略\r\n- **策略**:2.23.140.1.2.1(表示域名验证证书)\r\n### 3.6.4. CRL分发点\r\n- **URI**:[http://c.pki.goog/we2/dTM3-0hpWfE.crl(证书吊销列表的位置)](http://c.pki.goog/we2/dTM3-0hpWfE.crl%EF%BC%88%E8%AF%81%E4%B9%A6%E5%90%8A%E9%94%80%E5%88%97%E8%A1%A8%E7%9A%84%E4%BD%8D%E7%BD%AE%EF%BC%89)\r\n### 3.6.5. 签名算法和签名值\r\n- **摘要算法**:ecdsa-with-SHA256\r\n- **值**:30:45:02:21:00:ea:41...(略)(CA对证书内容的签名,CA私钥加密)\r\n# 4. 合法性校验\r\n## 4.1. 验证证书链\r\n证书链从根证书开始,并且证书链中的每一级证书所标识的实体都要为其下一级证书签名,而根证书自身则由证书颁发机构签名。客户端在验证证书链时,必须对链中所有证书的数字签名进行验证,直到达到根证书为止。\r\n\r\n详细步骤如下:\r\n- 获取颁发该证书的CA证书([http://i.pki.goog/we2.crt)](http://i.pki.goog/we2.crt%EF%BC%89)\r\n- 验证证书是由可信CA签发的(Google Trust Services是知名可信CA)\r\n- 确认完整的证书链直到根证书\r\n\r\n现代浏览器和操作系统会自动执行这些步骤:\r\n1. 接收服务器提供的证书链\r\n2. 检查每个证书是否由上一级证书正确签名\r\n3. 追溯到预装的可信根证书\r\n4. 检查证书是否过期或被吊销\r\n5. 如果验证成功,显示安全连接;失败则显示警告\r\n\r\n对于上述Google证书,完整的证书链应该是:\r\n- 终端实体证书:CN=[www.google.com](http://www.google.com)\r\n- 中间证书:C=US, O=Google Trust Services, CN=WE2\r\n- Google Trust Services根证书(已预装在大多数操作系统和浏览器中)\r\n以B站证书为例\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250401223628.png)\r\n## 4.2. 检查有效期\r\n确认当前日期在证书的有效期内(2025年1月6日至2025年3月31日)\r\n## 4.3. 验证域名\r\n确认Subject和SAN(Subject Alternative Name)中的域名与您访问的网站匹配([www.google.com)](http://www.google.com%EF%BC%89)\r\n## 4.4. 验证证书状态\r\n通过OCSP([http://o.pki.goog/we2)或CRL(http://c.pki.goog/we2/dTM3-0hpWfE.crl)检查证书是否被吊销](http://o.pki.goog/we2%EF%BC%89%E6%88%96CRL%EF%BC%88http://c.pki.goog/we2/dTM3-0hpWfE.crl%EF%BC%89%E6%A3%80%E6%9F%A5%E8%AF%81%E4%B9%A6%E6%98%AF%E5%90%A6%E8%A2%AB%E5%90%8A%E9%94%80)\r\n## 4.5. 验证签名\r\n使用CA的公钥验证证书上的签名,`openssl verify -CAfile we2.crt www.google.com.pem`\r\n### 4.5.1. 签名内容\r\n当验证证书签名时,系统会对证书的**所有内容**(除了签名本身)生成摘要,具体包括:\r\n- 版本号(Version)\r\n- 序列号(Serial Number)\r\n- 签名算法标识符(Signature Algorithm)\r\n- 颁发者信息(Issuer)\r\n- 有效期(Validity)\r\n- 证书主体信息(Subject)\r\n- 公钥信息(Subject Public Key Info)\r\n- 所有X.509 v3扩展内容\r\n实际上,这相当于证书的整个DER编码的**TBSCertificate**(To Be Signed Certificate)部分。\r\n### 4.5.2. 验证签名过程\r\n详细过程如下:\r\n1. 提取CA证书的公钥\r\n2. 使用CA公钥对签名(证书中的最后部分:`30:45:02:21:00:ea:41...`)进行解密,得到摘要值\r\n3. 使用摘要算法(SHA256)对TBSCertificate计算摘要值\r\n4. 比较两个计算的摘要是否相等\r\n> [!note]\r\n> ECDSA (Elliptic Curve Digital Signature Algorithm) 是一种数字签名算法,它基于椭圆曲线密码学原理。这是一种现代密码学技术,相比传统的RSA算法,可以使用更短的密钥提供同等级别的安全性\r\n#### 4.5.2.1. 检查证书透明度\r\n查询证书透明度日志,确认证书已被记录(SCT信息部分)\r\n#### 4.5.2.2. 检查密钥用途\r\n确认证书用途符合预期(此证书用于TLS Web服务器认证)\r\n# 5. 常见文件格式\r\n## 5.1. PEM(Privacy - Enhanced Mail)\r\n是一种常用的证书或私钥存储格式。它是一种文本格式,以-----BEGIN...-----开头,以-----END...-----结尾。内容主要包括Base64编码的数据。如果是PEM格式的公钥证书,其中就包含证书信息;若是PEM格式的私钥,就包含私钥数据。\r\n## 5.2. P12(也称为PFX)\r\n是一种文件格式,用于存储包含公钥、私钥和证书的加密信息包。它是一种二进制格式,主要用于在不同的应用程序和系统之间交换和存储加密密钥和相关证书。这种格式可以方便地将所有必要的安全组件(如个人证书、中间证书、根证书和与之匹配的私钥)打包在一起,并且可以使用密码进行保护,确保只有知道密码的授权用户才能访问其中的内容。\r\n## 5.3. JKS(Java Key Store)\r\n是Java的密钥库格式。它可以存储多种加密元素,包括私钥、公钥证书以及证书链。证书链是一组按顺序排列的证书,从服务器证书开始,到中间证书,最后是根证书。这些元素在JKS文件中被安全地保存起来,用于Java应用程序中的安全通信和身份验证。\r\n \r\n在Java应用程序(如Spring应用)中广泛用于配置SSL/TLS通信。例如,在基于Java的Web服务器中,服务器的私钥和证书可以存储在JKS文件中,然后在服务器启动时加载这个JKS文件来建立安全的HTTPS连接。\r\n\r\nJKS文件是二进制格式,并且可以通过密码进行保护。这意味着只有知道密码的用户才能访问和使用存储在其中的密钥和证书。这种加密保护有助于防止密钥和证书被未授权的访问和滥用。\r\n## 5.4. crt格式\r\n.crt文件通常用于存储证书。证书包含了公钥信息、证书所有者(如个人、公司或服务器)的身份信息、证书颁发机构(CA)的数字签名等。这些信息用于在网络通信等场景中验证身份。例如,在SSL/TLS加密通信中,服务器会将.crt文件中的公钥证书发送给客户端,客户端通过验证证书签名来确认服务器身份合法性。\r\n## 5.5. key格式\r\n.key文件主要用于存储私钥。私钥是加密系统中的关键部分,用于解密由对应的公钥加密的数据,以及进行数字签名。它必须严格保密,因为如果私钥泄露,可能会导致安全问题,如信息被解密或伪造数字签名。\r\n\r\n加密私钥\r\n```shell\r\n# --aes256是加密算法\r\nopenssl rsa -aes256 -in private_decrypted.key -out private_encrypted.key\r\n```\r\n\r\n解密私钥\r\n```shell\r\nopenssl rsa -in private_encrypted.key -out private_decrypted.key\r\n# 解密,输入密码后明文输出\r\nopenssl rsa -text -noout -in private_encrypted.key\r\n```\r\n\r\n\r\n# 6. ref\r\nhttps://pandaychen.github.io/2019/07/24/auth/\r\nhttps://www.cnblogs.com/enoc/p/tls-handshake.html\r\nhttps://www.nervos.org/zh/knowledge-base/understanding_ECDSA_(explainCKBot)\r\n"},{"id":"webauthn","title":"Webauthn","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":5,"slug":"webauthn","description":"在数字时代,密码已成为我们日常生活和在线活动中不可或缺的一部分。尽管互联网已经发展了20多年,许多方面都有了巨大的改进,但只有密码,还是20年前的用法。更准确的说,它的用户体验比 20 年前更差了。 1. 密码的强度现在要求越来越高,一般不能少于8个字符,还要包括特殊符号。 2. 除了密码,通常还有...","relativePath":"Tech/security/webauthn.md","rawContent":"在数字时代,**密码**已成为我们日常生活和在线活动中不可或缺的一部分。尽管互联网已经发展了20多年,许多方面都有了巨大的改进,但只有密码,还是20年前的用法。更准确的说,它的用户体验比 20 年前更差了。\r\n1. 密码的强度现在要求越来越高,一般不能少于8个字符,还要包括特殊符号。\r\n2. 除了密码,通常还有其他验证(短信、图片识别、OTP 一次性密码等等)。\r\n3. 为了避免被一锅端,我们还要为不同网站设置不同密码,在互联网如此发达的今天,要记住如此多的密码,属实有点困难\r\n\r\n然而,即使变得如此麻烦,**依然不能杜绝密码被盗、被破解、被钓鱼的风险**。为了解决这些问题,WebAuthn应运而生。\r\n# 1. WebAuthn简介\r\nWebAuthn,全称Web Authentication,是由FIDO 联盟(Fast IDentity Online Alliance)和 W3C(World Wide Web Consortium)联合制定的一套新的**身份认证标准**,旨在为网络身份验证提供一种更强大、更安全的方式,**使用户能够使用他们的设备(如手机、USB 密钥或生物识别器)来进行身份验证,而无需使用密码**。该标准于2019 年3月4日正式成为 W3C 的推荐标准。目前主流的浏览器已经支持 WebAuthn,包括 Chrome、Firefox、Edge 和 Safari,更详细的支持情况可以通过[https://webauthn.me/browser-support](https://webauthn.me/browser-support) 查看。\r\n\r\n> 注:FIDO联盟是一个非营利性组织,由Google、微软、苹果、三星、高通、芯片厂商、支付公司、银行、电信运营商、认证公司等组成,旨在为用户提供更安全、更简单的身份验证体验。_\r\n\r\n# 2. WebAuthn的工作原理\r\nWebAuthn的原理并不复杂,它的核心是基于非对称的加密技术。在WebAuthn中,用户的身份认证是通过**公钥和私钥来实现的**。这很像我们平常使用配置了公私钥的SSH登录服务器的过程,只不过WebAuthn 是在浏览器中实现的。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian202508091603500.png)\r\n\r\n# 3. WebAuthn的组成部分\r\nWebAuthn由以下三个组成部分组成:\r\n1. **用户代理(User Agent)**:用户代理是指浏览器或者其他支持WebAuthn的客户端,它负责与用户进行交互,收集用户的身份认证信息,并将其发送给服务器。\r\n2. **身份验证器(Authenticator)**:身份验证器是指用于生成公钥和私钥的设备,如手机、USB密钥或生物识别器。Windows Hello和macOS的Touch ID也都是常见的身份验证器。\r\n3. **Relying Party**:Relying Party 是指需要进行身份认证的网站或应用程序,它负责生成挑战(Challenge)并将其发送给用户代理,然后验证用户代理发送的签名结果。 \r\n\r\n上述三者在两个不同的用例(注册和认证)中协同工作,如下图所示。图中的各个实体之间的所有通信都由用户代理(通常是Web浏览器)处理。\r\n\r\n# 4. WebAuthn API\r\n涉及到两个主要的 API:\r\n- `navigator.credentials.create()`:用于在**注册**阶段生成公钥和私钥\r\n- `navigator.credentials.get()`:用于在**认证**阶段对服务端的挑战(Challenge)进行签名\r\n"},{"id":"Tech","title":"Tech","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"tech","description":"","relativePath":"Tech/Tech.md","rawContent":""},{"id":"CPU架构","title":"CPU架构","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":7,"slug":"cpu架构","description":"1. 主频 CPU 主频,即CPU的时钟频率,是衡量 CPU 运算速度的重要指标,以下从其定义、工作原理及与性能的关系等方面进行具体解释。 - 基本定义:CPU 主频表示 CPU 内数字脉冲信号振荡的速度,单位为赫兹(Hz)。通常所说的CPU 主频为2.4GHz、3.6GHz等,意味着CPU内部的时...","relativePath":"Tech/计算机组成原理/CPU架构.md","rawContent":"![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202502012204187.png)\r\n# 1. 主频\r\nCPU 主频,即CPU的时钟频率,是衡量 CPU 运算速度的重要指标,以下从其定义、工作原理及与性能的关系等方面进行具体解释。\r\n- **基本定义**:CPU 主频表示 CPU 内数字脉冲信号振荡的速度,单位为赫兹(Hz)。通常所说的CPU 主频为2.4GHz、3.6GHz等,意味着CPU内部的时钟信号每秒能产生2.4×10⁹次、3.6×10⁹次振荡,即一个CPU时钟周期的时间不足1ns。\r\n- **工作原理**:CPU的运行依赖时钟信号来同步各个部件的操作。时钟信号就像指挥家的节拍,每个节拍(时钟周期)内,CPU 完成特定的基本操作,如读取指令、解码指令、执行运算等。主频越高,每秒内的时钟周期数越多,CPU能完成的基本操作次数也越多,理论上运算速度就越快。\r\n- **与性能的关系**:一般情况下,在其他条件相同的前提下,主频越高,CPU 性能越强。例如,同架构、同核心数的两款CPU,主频3.0GHz的通常比2.5GHz的运算速度更快,在处理数据、运行程序等方面表现更出色。但CPU性能不仅取决于主频,还与核心数、缓存大小、架构、指令集等因素有关。如一些低主频、多核心的CPU,在多任务处理时,可能比高主频、单核心的 CPU 性能更好。\r\n- **动态调整**:现代CPU为了平衡性能和功耗,具备动态调整主频的技术,如Intel的睿频技术和AMD的智能加速技术。在系统负载高时,CPU可自动提升主频以提高性能;负载低时,降低主频减少功耗和发热,以实现节能和稳定运行。\r\n# 2. 寄存器分类\r\nCPU的每个核心都有自己的寄存器集,包含通用寄存器和特殊目的寄存器。通用寄存器用于存储通用数据和算术运算中间结果;特殊目的寄存器用于存储特定功能或控制状态信息,如程序计数器、栈指针、状态寄存器等 。\r\n在执行指令时,每个核心只访问和操作自己的寄存器集,可并行执行不同指令和操作,不会互相干扰或直接访问对方寄存器内容。当多个核心需通信或数据交换时,可使用共享内存或特定同步机制(如锁、原子操作等)来实现,寄存器本身并不直接用于多核心之间的通信 。\r\n\r\n不同类型的CPU,其内部寄存器的种类、数量以及寄存器的数值范围都是不同的。不过,根据功能的不同,可以将寄存器划分为下面几类:\r\n\r\n| 种类 | 功能 |\r\n| ----- | -------------------------------- |\r\n| 累加寄存器 | 存取运行的数据和运算后的数据 |\r\n| 标志寄存器 | 用于反应处理器的状态和运算结果的某些特征以及控制指令的执行 |\r\n| 程序计数器 | 程序计数器是用于存放下一条指令所在单元的地址的地方 |\r\n| 基址寄存器 | 存储数据内存的起始位置 |\r\n| 变址寄存器 | 存储基址的相对地址 |\r\n| 通用寄存器 | 存储任意数据 |\r\n| 指令寄存器 | 储存正在被运行的指令,CPU内部使用,程序无法对该寄存器进行读写 |\r\n| 栈寄存器 | 存储栈区域的起始位置 |\r\n\r\n> [!tip]\r\n> 其中**程序计数器、累加寄存器、标志寄存器、指令寄存器和栈寄存器**都只有一个,其他寄存器一般有多个。\r\n\r\n# 3. 程序计数器(PC)\r\n程序计数器(Program Counter)是用来存储下一条指令所在单元的地址。程序执行时,PC的初值为程序**第一条指令**的地址,在顺序执行程序时,控制器首先按程序计数器所指出的指令地址从内存中取出一条指令,然后分析和执行该指令,同时将PC的值加1指向下一条要执行的指令。可根据一个示例来看一下程序计数器的执行过程:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202502012213320.png)\r\n这是一段进行相加的操作,程序启动,在经过编译解析后会由操作系统把硬盘中的程序复制到内存中,示例中的程序将123和456执行相加操作,并将结果输出到显示屏上。由于使用机器语言难以描述,所以这里经过翻译后的结果,实际上每个指令和数据都可能分布在不同的地址上,但为了方便说明,把组成每一条的内存和数据放在一个内存地址上。\r\n地址0100是程序运行的起始位置。Windows等操作系统把程序从硬盘复制到内存后,会将程序计数器作为设定为起始位置0100,然后执行程序,每执行一条指令后,程序计数器的数据会增加1(或者直接指向下一条指令的地址),然后,CPU就会根据程序计数器的数值,从内存中读取命令并执行,也就是说,程序计数器控制程序的流程。\r\n"},{"id":"CPU缓存设计","title":"CPU缓存设计","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":18,"slug":"cpu缓存设计","description":"1. 缓存 Cache Memory也被称为Cache,是存储器子系统的组成部分,存放着程序经常使用的指令和数据,这就是Cache的传统定义。从广义的角度上看,Cache是快设备为了缓解访问慢设备延时的预留的Buffer,从而可以在掩盖访问延时的同时,尽可能地提高数据传输率。 快和慢是一个相对概念,...","relativePath":"Tech/计算机组成原理/CPU缓存设计.md","rawContent":"# 1. 缓存\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202502012201874.png)\r\nCache Memory也被称为Cache,是存储器子系统的组成部分,存放着程序经常使用的指令和数据,这就是Cache的传统定义。从广义的角度上看,Cache是快设备为了缓解访问慢设备延时的预留的Buffer,从而可以在掩盖访问延时的同时,尽可能地提高数据传输率。 快和慢是一个相对概念,与微架构(Microarchitecture)中的 L1/L2/L3 Cache相比, DDR内存是一个慢速设备;在磁盘 I/O 系统中,DDR确是快速设备,在磁盘I/O系统中,仍在使用DDR内存作为磁介质的Cache。在一个[微架构](https://zhida.zhihu.com/search?content_id=176854366&content_type=Article&match_order=2&q=%E5%BE%AE%E6%9E%B6%E6%9E%84&zd_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ6aGlkYV9zZXJ2ZXIiLCJleHAiOjE3Mzg1OTA0NTUsInEiOiLlvq7mnrbmnoQiLCJ6aGlkYV9zb3VyY2UiOiJlbnRpdHkiLCJjb250ZW50X2lkIjoxNzY4NTQzNjYsImNvbnRlbnRfdHlwZSI6IkFydGljbGUiLCJtYXRjaF9vcmRlciI6MiwiemRfdG9rZW4iOm51bGx9.pXrpPTnTBsbUSfy2_OYfLNj9VxGaNEihgFX_nbOzc3k&zhida_source=entity)中,除了有L1/L2/L3 Cache之外,用于[虚实地址](https://zhida.zhihu.com/search?content_id=176854366&content_type=Article&match_order=1&q=%E8%99%9A%E5%AE%9E%E5%9C%B0%E5%9D%80&zd_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ6aGlkYV9zZXJ2ZXIiLCJleHAiOjE3Mzg1OTA0NTUsInEiOiLomZrlrp7lnLDlnYAiLCJ6aGlkYV9zb3VyY2UiOiJlbnRpdHkiLCJjb250ZW50X2lkIjoxNzY4NTQzNjYsImNvbnRlbnRfdHlwZSI6IkFydGljbGUiLCJtYXRjaF9vcmRlciI6MSwiemRfdG9rZW4iOm51bGx9.V5T6uK0yGiBpj_piBLr4xttZvMOPty35tGGXAgL_mOM&zhida_source=entity)转换的各级TLB, MOB( Memory Ordering Buffers)、在指令流水线中的ROB,Register File和BTB等等也是一种Cache。我们这里的Cache,是狭义 Cache,是CPU流水线和主存储器的 L1/L2/L3 Cache。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202502012151163.png)\r\nL1,L2,L3 指的都是CPU的缓存,比内存快,但是很昂贵,所以用作缓存,CPU查找数据的时候首先在L1,然后看L2,如果还没有,就到内存查找一些服务器还有L3 Cache,目的也是提高速度。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202502012201274.png)\r\n\r\nL1 cache、L2 cache、L3 cache 速度快,主要和其与 CPU 的距离、存储容量、制造工艺及数据预取机制有关:\r\n- **物理位置靠近 CPU**:L1-L3 cache 在位置上都离 CPU 核心越来越近,尤其是L1 cache通常集成在CPU内核内。这使得CPU在访问缓存时,数据传输路径短,信号传输延迟低,能快速获取数据和指令,就像在身边的物品更容易快速拿到。\r\n- **较小的存储容量**:缓存容量相对主存小很多,L1 cache通常几十KB-几百 KB,L2 cache几百KB-几MB,L3 cache几MB-几十 MB。较小的容量使得缓存的存储结构更紧凑,CPU查找数据时遍历的存储单元少,就像在小房间找东西比大仓库更容易、更快。\r\n- **先进的制造工艺**:缓存一般采用与CPU相同或更先进的制造工艺,使用高速的存储单元,这些存储单元的电子信号切换速度快,能快速响应CPU的读写请求,实现高速的数据存取。\r\n## 1.1. L1 Cache\r\nL1 Cache(一级缓存)是CPU第一层高速缓存,分为数据缓存和指令缓存。**内置的L1高速缓存的容量和结构对CPU的性能影响较大**,不过高速缓冲存储器均由**静态RAM**组成,结构较复杂,在CPU管芯面积不能太大的情况下,L1级高速缓存的容量不可能做得太大。一般服务器CPU的L1缓存的容量通常在32—256KB。\r\n> SRAM 速度快,不过在相同面积下其容量比其他类型内存小,且成本较高。而 L1 cache 集成在 CPU 内部,作为 CPU 和较低速内存(如 DRAM )之间的缓存,对速度要求极高。SRAM 的高速特性契合 L1 cache 需求,能让其快速响应 CPU 的数据和指令读取请求,减少 CPU 等待时间,提升运行效率。\r\n### 1.1.1. 静态ram和动态ram\r\n静态 RAM(SRAM)和动态 RAM(DRAM)的区别如下:\r\n- **存储原理**:\r\n - **静态RAM**:每个存储单元由四到六个晶体管组成双稳态触发器来记忆信息。只要供电正常,触发器就能稳定保持所存储的数据,无需刷新 。\r\n - **动态RAM**:每个存储单元由一个晶体管和一个电容构成。利用电容上存储的电荷来表示数据,由于电容存在漏电现象,电荷会逐渐流失,所以需周期性刷新补充电荷,以维持数据的正确性 。\r\n- **读写速度**:\r\n - **静态RAM**:存储单元结构简单且无需刷新,数据存取速度快,能快速响应 CPU 的数据请求,常被用作高速缓存(如 L1 - L3 cache) 。\r\n - **动态RAM**:因存在刷新操作及存储结构相对复杂等因素,读写速度比静态RAM慢 。\r\n- **集成度与容量**:\r\n - **静态RAM**:每个存储单元由多个晶体管组成,占用芯片面积大,集成度低,存储容量相对较小 。\r\n - **动态RAM**:每个存储单元仅由一个晶体管和一个电容组成,占用芯片面积小,集成度高,可实现大容量存储,常见的计算机内存条多采用动态RAM 。\r\n- **功耗**:\r\n - **静态RAM**:存储单元始终处于稳定状态,只要供电就有电流消耗,功耗相对较高 。\r\n - **动态RAM**:仅在读写和刷新操作时消耗较多功率,平时电容保持电荷状态时功耗低,整体功耗比静态RAM低 。\r\n- **成本**:\r\n - **静态RAM**:因集成度低、制造工艺复杂、单位存储容量成本高,价格较贵 。\r\n - **动态RAM**:由于集成度高、单位存储容量成本低,在大容量存储需求下性价比高,价格相对便宜\r\n## 1.2. L2 Cache\r\nL2 Cache(二级缓存)是CPU的第二层高速缓存,分内部和外部两种芯片。内部的芯片二级缓存运行速度与主频相同,而外部的二级缓存则只有主频的一半。L2高速缓存容量也会影响CPU的性能,原则是越大越好,现在家庭用CPU容量最大的是512KB,而服务器和工作站上用CPU的L2高速缓存更高达256-1MB,有的高达2MB或者3MB。\r\n## 1.3. L3 Cache\r\nL3 Cache(三级缓存),分为两种,早期的是外置,现在的都是内置的。而实际作用即是,L3缓存的应用可以进一步降低内存延迟,同时提升大数据量计算时处理器的性能。降低内存延迟和提升大数据量计算能力,对游戏都很有帮助。而在服务器领域增加L3缓存,在性能方面仍然有显著的提升。具有较大L3缓存的配置利用物理内存会更有效,比较慢的磁盘I/O子系统,可以处理更多的数据请求。具有较大L3缓存的处理器,提供更有效的文件系统缓存行为及较短消息和处理器队列长度。\r\n## 1.4. 局部性原理\r\n缓存基于局部性原理,局部性有两种,即时间局部性和空间局部性。\r\n**时间局部性**:当一个数据被访问后,它很有可能在不久的将来被再次访问,比如循环代码中的数据或指令。\r\n**空间局部性**:当程序访问地址为x的数据时,很有可能会紧接着访问x周围的数据,比如遍历数组或指令的顺序执行。\r\n由于这两种局部性存在大多数的程序中,硬件系统可以很好地预测哪些数据可以放入缓存,从而可以运行得很好。\r\n## 1.5. 缓存一致性\r\n多CPU系统中,每个CPU核心通常有自己的私有缓存(如L1、L2缓存),而共享主内存。缓存的存在是为了加速数据访问,减少访问主存的延迟。但这也带来了问题:当一个CPU修改了自己缓存中的数据时,其他CPU的缓存中同一数据的副本就会变得不一致。如何确保所有CPU看到的内存视图一致,这就是缓存一致性需要解决的问题。\r\n\r\n缓存一致性要求满足以下几点:\r\n1. **写传播(Write Propagation)**:任何CPU对某个内存位置的写操作必须最终被其他CPU看到。\r\n2. **事务串行化(Transaction Serialization)**:所有CPU对同一内存位置的读写操作顺序在所有CPU看来是一致的。\r\n最著名的缓存一致性协议应该是MESI协议,MESI代表缓存行的四种状态:\r\n- **Modified(已修改)**:缓存行已被修改,与主存不同,且只有当前缓存拥有该数据。\r\n- **Exclusive(独占)**:缓存行与主存一致,且未被其他缓存持有。\r\n- **Shared(共享)**:缓存行与主存一致,可能被多个缓存共享。\r\n- **Invalid(无效)**:缓存行数据无效,需要重新从主存或其他缓存获取。\r\n### 1.5.1. 状态转换\r\n**CPU A写数据**:\r\n 1. 若原状态为S或I→发送“无效化”信号给其他缓存,只有收到所有确认后,将状态改为M。\r\n 2. 若原状态为E→直接改为M。\r\n 3. 假设CPU A和CPU B同时发起对同一缓存行的写请求,则总线控制器(或目录协议)会序列化请求,只允许一个CPU先获得独占权。这种序列化是硬件层面的。\r\n**CPU B读数据**:\r\n 1. 若其他缓存有M状态→触发回写主存,并转为S状态。\r\n 2. 若其他缓存有S/E状态→共享数据,状态转为S。\r\n### 1.5.2. 协议变种\r\n- **MOESI**:ARM架构使用,增加Owned(O)状态,允许缓存持有修改数据并与其他缓存共享,延迟写回主存。\r\n- **MESIF**(Intel使用):x86架构,增加Forward(F)状态,指定一个缓存作为数据提供者,减少总线流量。\r\n### 1.5.3. 缓存一致性实现方式\r\n#### 1.5.3.1. 监听式协议(Snooping Protocol)\r\n- **适用场景**:基于总线的小规模多核系统(如单插槽CPU)。\r\n- **原理**:所有缓存监听总线上的事务(如读/写请求),根据规则更新自身状态。\r\n- **示例**:\r\n - CPU A写数据→广播“无效化”消息→其他缓存标记该数据为I。\r\n#### 1.5.3.2. 目录式协议(Directory Protocol)\r\n- **适用场景**:NUMA架构的大规模多处理器系统(如多路服务器)。\r\n- **原理**:中央目录记录缓存行状态(如哪些节点缓存了数据),减少广播开销。\r\n- **步骤**:\r\n 1. CPU请求数据→查询目录获取持有者列表。\r\n 2. 目录协调数据传递和状态更新(如发送无效化信号给特定节点)。\r\n#### 1.5.3.3. 性能分析\r\n##### 1.5.3.3.1. 写独占缓存时间\r\n处于E状态的缓存写入时间应该非常快,等同于本地缓存的写入延迟,不需要总线交互,因此时间大约是1到几个CPU周期,具体取决于处理器的设计。\r\n- L1缓存:**1-3个CPU周期**(约0.3-1 ns,假设3 GHz主频)。 \r\n- L2缓存:**5-10个周期**(约1.7-3.3 ns)。\r\n- L3缓存:**20-40个周期**(约6.7-13.3 ns)。\r\n> 3GHZ主频,一个CPU周期大约为$1/(3*10^9)=0.3ns$\r\n##### 1.5.3.3.2. 写共享缓存的时间组成\r\n1. **总线仲裁延迟**:多个核心竞争总线时的排队时间(通常**10-50纳秒**,取决于总线负载)。\r\n2. **信号传播延迟**:广播`Invalidate`信号到所有缓存控制器的时间(现代CPU片上互联延迟约**5-20纳秒**)。\r\n3. **确认响应延迟**:等待其他CPU确认无效化完成的时间(约**20-100纳秒**,取决于核心数量和拓扑)。\r\n**总延迟范围:**\r\n- **低竞争场景**:约**30-100纳秒**。\r\n- **高竞争场景**(如多核争抢同一缓存行):可能超过**200纳秒**\r\n##### 1.5.3.3.3. 直接写主存时间组成\r\n1. **DRAM访问延迟**:现代DDR4/DDR5内存的典型延迟为**70-100纳秒**。\r\n2. **总线传输时间**:数据从内存控制器到CPU的传输(约 **20-50纳秒**)。\r\n3. **协议开销**:若涉及缓存一致性,仍需总线交互(如回写脏数据)。\r\n**总延迟范围**:\r\n- **显式写主存**(非缓存):约 **100-150纳秒**。\r\n- **缓存未命中后写回**:可能叠加缓存加载时间,总延迟 **200纳秒以上**。\r\n#### 1.5.3.4. 对比\r\n| **指标** | **监听式协议(Snooping)** | **目录式协议(Directory)** |\r\n| -------- | -------------------- | -------------------- |\r\n| **扩展性** | 差(总线带宽限制,适合≤32核) | 优(目录记录状态,适合大规模多核) |\r\n| **总线流量** | 高(广播所有消息) | 低(仅通知相关节点) |\r\n| **典型应用** | 桌面CPU(如Intel Core系列) | 服务器CPU(如AMD EPYC) |\r\n| **延迟** | 低(直接总线访问) | 较高(需查询目录) |\r\n### 1.5.4. 内存屏障(Memory Barrier)\r\n- **作用**:强制内存操作顺序,防止CPU/编译器重排序破坏一致性,同时确保缓存中的数据及时刷新到主内存,以及从主内存中读取最新的数据。\r\n- **类型**:\r\n - **写屏障**:确保屏障前的写操作先于屏障后的写操作提交。\r\n - **读屏障**:确保屏障后的读操作能看到屏障前所有写操作的结果。\r\n```c\r\n// 线程1\r\ndata = 42;\r\nmemory_barrier(); // 确保data写入在flag之前\r\nflag = 1;\r\n\r\n// 线程2\r\nwhile (flag == 0); // 自旋等待\r\nmemory_barrier(); // 确保读取flag后能读取到最新的data\r\nprintf(\"%d\", data);\r\n```\r\n#### 1.5.4.1. 指令重排\r\n现代的编译器和CPU为了提高程序的执行性能,会对指令进行重排序。指令重排序是指编译器或CPU在不改变程序**单线程执行结果**的前提下,对指令的执行顺序进行调整。\r\n> 在没有内存屏障的情况下,编译器或CPU可能会对线程1中的指令进行重排序,将 `flag = 1;` 提前到 `data = 42;` 之前执行。当发生这种重排序时,线程2可能会在 `data` 还未被正确赋值为`42`时,就通过检查 `flag` 的值(此时 `flag` 已经被置为 `1`)进入后续代码读取 `data`,从而读到错误的值。\r\n> \r\n> 线程 1 中的内存屏障保证了 `data` 的写入操作一定在 `flag` 的写入操作之前完成,并且**数据会被刷新到主内存**。线程 2 中的内存屏障保证了在读取 `flag` 之后,能从主内存中读取到最新的 `data` 值。\r\n### 1.5.5. 软件协同机制\r\n#### 1.5.5.1. 原子操作\r\n- **CAS(Compare-And-Swap)**:硬件支持的原子指令,确保“读-改-写”操作不可分割。\r\n```c\r\n// 原子地比较并更新值\r\nint atomic_cas(int* ptr, int expect, int new_val) {\r\n// 若*ptr == expect,则*ptr = new_val,返回旧值\r\n}\r\n```\r\n#### 1.5.5.2. 锁机制\r\n- **自旋锁(Spinlock)**:忙等待锁,适用于短临界区。\r\n- **互斥锁(Mutex)**:阻塞等待锁,减少CPU空转。\r\n#### 1.5.5.3. 无锁编程\r\n基于原子操作和内存屏障实现数据结构(如无锁队列),避免锁竞争。"},{"id":"CPU调度策略","title":"CPU调度策略","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"cpu调度策略","description":"1. MLFQ(Multi-Level Feedback Queue) - 系统中存在多个就绪队列,每个队列有不同的优先级,高优先级队列中的进程会优先获得 CPU 执行。 - 进程在队列之间可以移动,根据其行为(如 CPU 占用时间、I/O 操作等)动态调整优先级。 1.1. 规则 1.1.1. 优...","relativePath":"Tech/计算机组成原理/CPU调度策略.md","rawContent":"# 1. MLFQ(Multi-Level Feedback Queue)\r\n- 系统中存在多个就绪队列,每个队列有不同的优先级,高优先级队列中的进程会优先获得 CPU 执行。\r\n- 进程在队列之间可以移动,根据其行为(如 CPU 占用时间、I/O 操作等)动态调整优先级。\r\n## 1.1. 规则\r\n### 1.1.1. 优先规则\r\n1. 如果进程A的优先级高于进程B,则A先运行\r\n2. 如果进程A和进程B的优先级相同,则采用round-robin\r\n### 1.1.2. 初始队队列\r\n1. 新创建的进程进入最高优先级队列\r\n### 1.1.3. 降级规则\r\n1. 每个进程在某个优先级队列中的配额是固定的,如果一个进程用完了其在当前队列中的时间配额,则降低其优先级,移入下一个低优先级队列。\r\n降级规则是为了防止长任务一直占用 CPU,让短任务和交互式任务也有机会运行。\r\n### 1.1.4. 升级规则\r\n1. 如果一个进程在等待 I/O 完成后回到就绪队列,可以将其优先级提高。\r\n2. 如果一个进程长时间没有获得 CPU 时间,可以将其优先级提高。\r\n升级规则的目的是为了提高交互式任务的响应速度,并避免长任务被“饿死”。"},{"id":"blog_online","title":"Blog Online","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"blog-online","description":"","relativePath":"Template/blog_online.md","rawContent":"---\r\ntitle: 文章列表\r\ndate: \r\ncategories: \r\ntags:\r\n---\r\n\r\n\r\n\r\n\r\n"},{"id":"TODO","title":"TODO","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"todo","description":"- BGP实践,BGP PEER建立 - 核隔离与CGROUP - NUMA - 中断 - 硬件卸载 - 大页 - etcd 1. cloud wan ccn一个vpc支持创建几个attachment ccn路由同步,会同步到未关联的attachment吗?默认会同步所有路由表吗?","relativePath":"TODO.md","rawContent":"\n- BGP实践,BGP PEER建立\n- 核隔离与CGROUP\n- NUMA\n- 中断\n- 硬件卸载\n- 大页\n- etcd\n\n\n# 1. cloud wan\nccn一个vpc支持创建几个attachment\nccn路由同步,会同步到未关联的attachment吗?默认会同步所有路由表吗?"},{"id":"Icloud","title":"Icloud","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"icloud","description":"使用土区账号购买50G icloud,平均每个月2.5块 1. 土区账号登录icloud 2. 国区账号登录apple store 3. 闲鱼购买土区礼品卡充值到土区账号 4. 土区账号购买icloud 5. 过去账号通过airdop发送邀请土区账号加入家庭组 6. 土区账号共享icloud设置家庭...","relativePath":"Tool/CloudDrive/Icloud.md","rawContent":"\r\n使用土区账号购买50G icloud,平均每个月2.5块\r\n1. 土区账号登录icloud\r\n2. 国区账号登录apple store\r\n3. 闲鱼购买土区礼品卡充值到土区账号\r\n4. 土区账号购买icloud\r\n5. 过去账号通过**airdop**发送邀请土区账号加入家庭组\r\n6. 土区账号共享icloud设置家庭共享存储空间\r\n7. 做完以上步骤后,就可以退出土区账号了"},{"id":"Excel","title":"Excel","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"excel","description":"1. 将多个sheet join合并 Power Query自动化合并(适合多列/大数据) 1. 导入数据到Power Query: - 在Excel中点击数据 > Power Query > 获取数据,加载Sheet1和Sheet2。 2. 合并查询: - 选择Sheet1作为主表,与Sheet2...","relativePath":"Tool/Excel.md","rawContent":"---\naliases:\n - Excel\n---\n# 1. 将多个sheet join合并\nPower Query自动化合并(适合多列/大数据)\n1. **导入数据到Power Query**:\n - 在Excel中点击数据 > Power Query > 获取数据,加载Sheet1和Sheet2。\n2. **合并查询**:\n - 选择Sheet1作为主表,与Sheet2按“课程”列进行**左连接(Left Join)**。\n3. **展开所有属性**:\n - 在合并后的查询中,展开Sheet2的所有属性列(自动生成N列)。\n4. **导出到新工作表**:\n - 点击**加载到Excel**,生成含N列的新表格。"},{"id":"vnc远程桌面","title":"Vnc远程桌面","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"vnc远程桌面","description":"","relativePath":"Tool/Linux/vnc远程桌面.md","rawContent":"```shell\r\n# 安装依赖\r\napt install wget apt-transport-https gnupg2\r\n# 安装桌面环境\r\napt install xfce4 xfce4-goodies\r\n# 安装tigervnc\r\napt install tigervnc-standalone-server\r\n\r\n# 编辑.vnc/xstartup\r\n---\r\n#!/bin/sh\r\n# Start up the standard system desktop\r\nunset SESSION_MANAGER\r\nunset DBUS_SESSION_BUS_ADDRESS\r\n/usr/bin/startxfce4\r\n---\r\n\r\n# 启动并设置密码\r\nvncserver\r\n\r\n\r\n# ssh加密,使用tmux执行\r\nssh -L 59000:127.0.0.1:5901 -C -N -l shinerio 192.168.85.2\r\n\r\n# 安装novnc以支持浏览器访问\r\ngit clone git@github.com:novnc/noVNC.git\r\n# 进入项目\r\ncd noVNC\r\n# 启动项目\r\nchmod +x ./utils/novnc_proxy\r\n./utils/novnc_proxy --listen 8080 --vnc 127.0.0.1:59000\r\n\r\n# 浏览器输入ip:8080/vnc.html访问\r\n```\r\n\r\n"},{"id":"总览","title":"总览","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"总览","description":"","relativePath":"Tool/Macos/总览.md","rawContent":""},{"id":"clash-for-linux","title":"Clash For Linux","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"clash-for-linux","description":"","relativePath":"Tool/Nas/clash-for-linux.md","rawContent":"```shell\r\ngit clone --branch master --depth 1 https://ghfast.top/https://github.com/nelvko/clash-for-linux-install.git && cd clash-for-linux-install && sudo bash install.sh\r\n```"},{"id":"jupyter安装","title":"Jupyter安装","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"jupyter安装","description":"z从sqlite官网,下载安装最新版本sqlite3 清理旧版本的sqlite3 安装并编译python环境 配置jupyter lab 添加python环境 1. 格式转换","relativePath":"Tool/Nas/jupyter安装.md","rawContent":"z从sqlite官网`cnblogs.com/staff/p/12763019.html`,下载安装最新版本sqlite3\r\n\r\n清理旧版本的sqlite3\r\n```\r\n# 临时重命名或移除系统旧版本(谨慎操作)\r\nsudo mv /usr/lib64/libsqlite3.so /usr/lib64/libsqlite3.so.backup\r\nsudo mv /usr/lib64/libsqlite3.so.0 /usr/lib64/libsqlite3.so.0.backup\r\n\r\n# 更新链接器配置,确保 /usr/local/lib 优先\r\necho \"/usr/local/lib\" | sudo tee -a /etc/ld.so.conf.d/local.conf\r\nsudo ldconfig\r\n\r\n# 验证链接器现在找到的是正确版本\r\nldconfig -p | grep sqlite\r\n```\r\n\r\n安装并编译python环境\r\n```\r\nyum install libffi-devel\r\n\r\n./configure \\\r\n --enable-optimizations \\\r\n CPPFLAGS=\"-I/usr/local/include\" \\\r\n LDFLAGS=\"-L/usr/local/lib\" \\\r\n SQLITE3_CFLAGS=\"-I/usr/local/include\" \\\r\n SQLITE3_LIBS=\"-L/usr/local/lib -lsqlite3\"\r\n\r\nmake clean\r\nmake -j$(nproc)\r\nmake altinstall\r\n```\r\n配置jupyter lab\r\n```\r\njupyter lab --generate-config\r\njupyter lab password\r\njupyter lab --ip=0.0.0.0 --port=14445 --no-browser --allow-root\r\n```\r\n\r\n添加python环境\r\n```\r\n/usr/local/bin/pip3.13 install ipykernel\r\n/usr/local/bin/python3.13 -m ipykernel install --user --name=python313\r\n```\r\n\r\n# 1. 格式转换\r\n```\r\njupyter nbconvert --to html vector.ipynb\r\njupyter nbconvert --to markdown vector.ipynb\r\njupyter nbconvert --to pdf vector.ipynb\r\n```"},{"id":"movie-pilot","title":"Movie Pilot","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"movie-pilot","description":"1. docker 安装 注意要通过环境变量配置代理,否则无法访问themoviedb或thetvdb等 2. 下载与整理 可以通过软连接目录的方式达到下载目录和mp的/media目录一致。 3. ref https://wiki.movie-pilot.org/zh/install","relativePath":"Tool/Nas/movie-pilot.md","rawContent":"# 1. docker 安装\r\n注意要通过环境变量配置代理,否则无法访问themoviedb或thetvdb等\r\n```shell\r\n docker run -itd \\\r\n --name moviepilot-v2 \\\r\n --hostname moviepilot-v2 \\\r\n --network host \\\r\n -v /vol2/1000/MoviePilot:/media \\\r\n -v /moviepilot-v2/config:/config \\\r\n -v /moviepilot-v2/core:/moviepilot/.cache/ms-playwright \\\r\n -v /var/run/docker.sock:/var/run/docker.sock:ro \\\r\n -e 'NGINX_PORT=3000' \\\r\n -e 'PORT=3001' \\\r\n -e 'PUID=0' \\\r\n -e 'PGID=0' \\\r\n -e 'UMASK=000' \\\r\n -e 'TZ=Asia/Shanghai' \\\r\n -e 'SUPERUSER=admin' \\\r\n -e 'http_proxy=http://127.0.0.1:7890' \\\r\n -e 'https_proxy=http://127.0.0.1:7890' \\\r\n -e 'PROXY_HOST=http://127.0.0.1:7890' \\\r\n --restart always \\\r\n jxxghp/moviepilot-v2:latest\r\n```\r\n\r\n# 2. 下载与整理\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250521234742.png)\r\n\r\n可以通过软连接目录的方式达到下载目录和mp的/media目录一致。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory20250521234855.png)\r\n\r\n# 3. ref\r\nhttps://wiki.movie-pilot.org/zh/install"},{"id":"movie_bot","title":"Movie Bot","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"movie-bot","description":"","relativePath":"Tool/Nas/movie_bot.md","rawContent":"```cmd\r\ndocker run --env=LICENSE_KEY=xxx --env=WORKDIR=/data --volume=D:\\Movie-Robot:/data --volume=D:\\Media:/video --volume=/data --workdir=/app -p 1329:1329 --restart=always -d yipengfei/movie-robot:latest\r\n```\r\n\r\n\r\n\r\n\r\n"},{"id":"NAS搭建","title":"NAS搭建","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":4,"slug":"nas搭建","description":"1. 主机硬件 - 主板:微星B360M Mortar - CPU:i3-8300t - 内存:金士顿骇客8G 2 DDR4 - SSD:三星980 NVMe PCIe3.0 500G - 西数:红盘4T - 散热器:利民AXP90 X47 - 电源:台达VX350 - 机箱:先马米立方matx 2...","relativePath":"Tool/Nas/NAS搭建.md","rawContent":"# 1. 主机硬件\r\n\r\n- 主板:微星B360M Mortar\r\n- CPU:i3-8300t\r\n- 内存:金士顿骇客8G * 2 DDR4\r\n- SSD:三星980 NVMe PCIe3.0 500G\r\n- 西数:红盘4T\r\n- 散热器:利民AXP90 X47\r\n- 电源:台达VX350\r\n- 机箱:先马米立方matx\r\n\r\n# 2. ssh登录\r\nubuntu系统,设置仅支持ssh秘钥登录,保证安全性\r\n\r\n# 3. 开启samba共享文件\r\n```shell\r\nvim /etc/samba/smb.conf\r\n```\r\n\r\n# 4. plex\r\n配置ssl加密,在let's encrypt申请证书(p12格式,默认包含公钥、私钥和证书)。\r\n\r\n> 更换绑定证书后需要浏览器清理一下证书缓存。\r\n\r\n外网访问plex统一使用https通道,内网还保留http通道。\r\n# 5. movie-robot\r\n```shell\r\ndocker run -itd --name=movie-robot --restart always --network host -p 1329:1329 -v /home/shinerio/movie-robot:/data -v /home/shinerio/Downloads:/Downloads -v /home/shinerio/data/Media/Movie:/Movie -v /home/shinerio/data/Media/TvShow:/TvShow --env 'LICENSE_KEY=存储在1password' yipengfei/movie-robot:latest\r\n```\r\n\r\n# 6. shadowsocks打通隧道\r\n通过ddns绑定公网ip,端口只映射几个必要的、能够保证安全的端口,其他服务在外网通过ss代理的方式访问。内网主机配置自定义域名解析到192.168.85.0/24内网IP,识别外网环境强制192.168.85.0/24这个网段走家庭网络专用ss代理服务器。\r\n\r\n## 6.1. 端口映射\r\n\r\n| port | service | 安全性 |\r\n| ----- | --------- | --------------------------------------------------------- |\r\n| 50000 | tomcat | 使用tomcat用户启动,无login权限,无sudo权限;三重密码保护 |\r\n| 14443 | ss-server | 高安全性加密算法,复杂密码 |\r\n| 32400 | plex | plex用户启动,无login权限,无sudo权限;两步认证 |\r\n\r\n## 6.2. 域名分配\r\n设备使用自定义域名解析到内网ip,配置场景模式外网环境强制走ss代理,14443端口回家,家庭环境直接走内网,不过路由。\r\n\r\n| ip地址 | 自定义域名 |\r\n| -------------- | --------------- |\r\n| 192.168.85.105 | shinerio.nas |\r\n| 192.168.85.2 | shinerio.server |\r\n\r\n## 6.3. 外网访问使用的所有自定义域名ss-server也必须可以解析\r\n```shell\r\n# vim /etc/hosts添加\r\n192.168.85.2 shinerio.server\r\nfe80::211a:a1f9:e853:b548 shinerio.server\r\n192.168.85.105 shinerio.nas\r\nfe80::20c:29ff:feb5:ff66 shinerio.nas\r\n```\r\n需要同时添加ipv6的解析,否则ss建连会比较慢\r\n```bash\r\nApr 2 19:40:53 shinerio-ubuntu /usr/local/bin/ss-server[27224]: successfully resolved shinerio.nas\r\nApr 2 19:40:53 shinerio-ubuntu /usr/local/bin/ss-server[27224]: new connection to remote, 1 opened remote connections\r\nApr 2 19:40:53 shinerio-ubuntu /usr/local/bin/ss-server[27224]: failed to lookup v6 address Timeout while contacting DNS servers\r\n```\r\n\r\n# 7. 远程控制\r\n## 7.1. xrdp远程桌面\r\n```shell\r\n# 安装\r\napt install xrdp\r\n# 启动\r\nsystemctl start xrdp\r\n# 开机启动\r\nsystemctl enable xrdp\r\n```\r\n解决黑屏\r\n```shell\r\n# 编辑\r\nvim /etc/xrdp/startwm.sh\r\n# 添加配置\r\nunset DBUS_SESSION_BUS_ADDRESS\r\nunset XDG_RUNTIME_DIR\r\n\r\n# 优化桌面,不做任何配置,启动之后的桌面是非常别扭的,因为是Gnome的原始桌面,没有左侧的任务栏,窗口也没有最小化按钮,等等一些列问题\r\nvim ~/.xsessionrc\r\n\r\n# 添加:\r\nexport GNOME_SHELL_SESSION_MODE=ubuntu\r\nexport XDG_CURRENT_DESKTOP=ubuntu:GNOME\r\nexport XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg\r\n\r\n# 重启\r\nsystemctl restart xrdp\r\n```\r\n\r\n## 7.2. 通过Guacamole实现web端控制\r\n```shell\r\n# 默认情况下guacamole只监听本地ip\r\n# /etc/guacamole/guacd.conf\r\n[server]\r\nbind_host = 192.168.85.2\r\nbind_port = 4822\r\n```\r\n配置登录信息\r\n```shell\r\nvim /etc/guacamole/user-mapping.xml\r\n```\r\n为了安全尽量不要配置密码,在登录后手动输入\r\n```xml\r\n\r\n #登录界面账号密码\r\n\r\n \r\n rdp #RDP协议配置\r\n 192.168.85.2 #远程主机IP\r\n 3389 #rdp 默认端口\r\n your-username #远程主机用户\r\n true\r\n \r\n \r\n\r\n```\r\n重启\r\nsystemctl restart guacd\r\n\r\n# 8. 群晖(虚拟机)\r\n使用arpl安装。设置复杂密码+两步认证+开启自动封锁\r\n\r\n## 8.1. 安装webDav server\r\n\r\n## 8.2. 设置仅支持ssh秘钥登录\r\n```\r\n# sshd对文件夹权限校验严格,需要将以下目录权限设置正确\r\nchmod 755 /homes/shinerio \r\nchmod 700 ~/.ssh \r\nchmod 600 ~/.ssh/authorized_keys\r\n```\r\n\r\n## 8.3. 安装git备份obsidian笔记\r\n```shell\r\n# 增量同步数据\r\nrsync -r shinerio@192.168.85.2:/home/shinerio/data/Documents/obsidian /var/services/homes/shinerio/Document/note\r\n```\r\n\r\n# 9. 分工原则\r\n\r\nubuntu: 媒体中心,存储电视、电影,通过web控制端兼具远程办公能力\r\nnas: photo备份(google photo同步备份),重要资料备份中心,document目录1自动同步google drive云备份\r\nobsidian:\r\n1. 全平台所有资料存储本地保存,通过gitee同步。\r\n2. 苹果是主力写作平台,通过icloud同步,同时google drive同步"},{"id":"openwrt","title":"Openwrt","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"openwrt","description":"","relativePath":"Tool/Nas/openwrt.md","rawContent":"```shell\r\ndocker network create -d macvlan --subnet=192.168.85.0/24 --gateway 192.168.85.1 -o parent=eth0 mac-net\r\ndocker pull esirpg/buddha:latest\r\ndocker run -d \\\r\n --restart always \\\r\n --name esirpg-buddha \\\r\n --privileged \\\r\n --network mac-net \\\r\n --ip=192.168.85.10 \\\r\n esirpg/buddha:latest \\\r\n /sbin/init\r\n```"},{"id":"Ubuntu系统备份","title":"Ubuntu系统备份","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"ubuntu系统备份","description":"1. 备份 2. 还原 如果当前启动无法启动,可以通过live cd来启动并执行恢复操作","relativePath":"Tool/Nas/Ubuntu系统备份.md","rawContent":"# 1. 备份\r\n\r\n```shell\r\ndf -h\r\n# 数据盘挂载在/home/shinerio/data\r\n# /dev/sda 3.6T 1.5T 2.0T 43% /home/shinerio/data\r\ntar cvpzf /home/shinerio/data/backup.tar.gz --exclude=/proc --exclude=/lost+found --exclude=/mnt --exclude=/sys --exclude=/media --exclude=/home/shinerio /\r\n```\r\n\r\n# 2. 还原\r\n\r\n```bash\r\ntar xcpfz backup.tar.gz -C /\r\nmkdir proc \r\nmkdir lost+found \r\nmkdir mnt \r\nmkdir sys\r\n```\r\n如果当前启动无法启动,可以通过live cd来启动并执行恢复操作"},{"id":"xiaoya","title":"Xiaoya","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"xiaoya","description":"windows下必须使用bridge模式 1. docker安装 2. 定时清理 3. 获取元数据 在wsl的ubuntu子系统中执行 4. 参考","relativePath":"Tool/Nas/xiaoya.md","rawContent":"windows下必须使用bridge模式\r\n# 1. docker安装\r\n```bash\r\n# bridge模式一键安装\r\ndocker run -d --restart=always --name=\"xiaoya\" -p 5678:80 -p 2345:2345 -p 2346:2346 -v D:\\Media\\xiaoya:/data xiaoyaliu/alist:latest\r\n# host模式一键安装\r\nbash -c \"$(curl http://docker.xiaoya.pro/update_new.sh)\" -s host\r\n```\r\n\r\n# 2. 定时清理\r\n\r\n```shell\r\nbash -c \"$(curl -s https://xiaoyahelper.ddsrem.com/aliyun_clear.sh | tail -n +2)\" -s 3\r\n```\r\n\r\n# 3. 获取元数据\r\n在wsl的ubuntu子系统中执行\r\n```shell\r\n# 生成配置,windows下必须使用bridge模式\r\nbash -c \"$(curl http://docker.xiaoya.pro/emby_new.sh)\" -s --config_dir=/mnt/d/Media/xiaoya --media_dir=/mnt/d/Media/xiaoya/media --action=generate_config\r\n# 执行安装\r\nbash -c \"$(curl http://docker.xiaoya.pro/emby_new.sh)\" \r\n\r\n```\r\n\r\n# 4. 参考\r\n[小雅Xiaoya TVbox/Jellyfin/EMBY 进阶之单独安装 Docker /群晖 独家保姆级教程 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/673584505#:~:text=App%20iOS%E3%80%81iPadOS%E3%80%81tvOS%E3%80%81MacOS%20PC%20Android%20TV,%20%20%20%0ADivHub%20%E2%88%9A)\r\n[小雅媒体库终级板emby+infuse | KChen’s Blog](https://blog.kchen.cc/article/xiaoya-emby-media)"},{"id":"Advanced Table","title":"Advanced Table","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"advanced-table","description":"obsidian 功能 - 自动化格式表格 - Excel样式的表格导航,即使用Tab和Enter在行和列之间导航 - 对指定的列进行函数求值 - 添加、删除、移动行和列 - 设置列的对齐方式 - 对指定列进行排序 - 将表格导出为CSV格式 公式 基本格式如下: 如中的 代表最后一行、第二列,右边...","relativePath":"Tool/Note/Obsidian/Advanced Table.md","rawContent":"#obsidian \r\n# 功能\r\n- 自动化格式表格\r\n- Excel样式的表格导航,即使用Tab和Enter在行和列之间导航\r\n- 对指定的列进行函数求值\r\n- 添加、删除、移动行和列\r\n- 设置列的对齐方式\r\n- 对指定列进行排序\r\n- 将表格导出为CSV格式\r\n\r\n# 公式\r\n\r\n[公式](https://github.com/tgrosinger/md-advanced-tables/blob/main/docs/formulas.md)基本格式如下:\r\n\r\n``\r\n\r\n如``中的 `@>$2` 代表最后一行、第二列,右边的 `sum` 代表计算,整个公式意思是右边的计算部分放到左边指定单元格里。\r\n\r\n## 单元格引用\r\n\r\n### 绝对引用\r\n- 指定行\r\n - `@n` 代表第 n 行\r\n - `@<` 代表第一行\r\n - `@>` 代表最后一行\r\n - `@I` 表示表头与内容分隔线\r\n- 指定列\r\n - `$n` 表示第 n 列\r\n - `$<` 表示第一列\r\n - `$>` 表示最后一列\r\n\r\n### 相对引用\r\n\r\n相对引用意思是引用单元格在当前单元格的某个位置。\r\n\r\n- `@-1` 表示引用单元格在当前单元格同一列,但在最后一行\r\n- `$+1` 表示引用单元格在当前单元格同一行,但在最后右边两列\r\n\r\n使用引用单元格时,行或列(和当前单元格相同部分)往往是不用写明的。\r\n\r\n### 范围引用\r\n\r\n`@I..@-1` 这样两个点表示引用一个范围。\r\n\r\n> 注意,范围只允许行到行、列到列、单元格到单元格。\r\n\r\n| 姓名 | 成绩 |\r\n| ---- | ---- |\r\n| 张一 | 100 |\r\n| 张二 | 200 |\r\n| 张三 | 300 |\r\n| 综合 | 600 |\r\n\r\n"},{"id":"Appearance","title":"Appearance","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"appearance","description":"打开笔记仓库的 .obsidian 文件夹,其中如果没有 snippets 文件夹则创建。在 Obsidian 中的 CSS snippets 都是以 .css 的档案格式储存在特定的文件夹。如果你有将 CSS snippets 放到该文件夹,就会在 Obsidian 显示开关。 1. 别人给你的,...","relativePath":"Tool/Note/Obsidian/Appearance.md","rawContent":"打开笔记仓库的 .obsidian 文件夹,其中如果没有 snippets 文件夹则创建。在 Obsidian 中的 CSS snippets 都是以 .css 的档案格式储存在特定的文件夹。如果你有将 CSS snippets 放到该文件夹,就会在 Obsidian 显示开关。\r\n1. 别人给你的,或者你看到不错的代码片段(CSS)文件,放进 snippets 文件夹就行。或者自己建立一个 CSS 文件,书写你自己的样式当然需要一点 CSS 代码基础。\r\n2. Obsidian > 设置 > 外观,最后一项【CSS 代码片段】,刷新一下,会显示出新增的文件,把后面的切换按钮打开即可。\r\n```css\r\n/* 对引用进行设计 */\r\nblockquote {\r\n border-left: 4px solid #4caf50!important; /* 鲜明的绿色边界 */\r\n background-color: #e8f5e9!important; /* 浅绿色背景 */\r\n color: #2e7d32!important; /* 引用文本的深绿色 */\r\n padding: 13px; /* 内边距 */\r\n margin: 16px 0; /* 外边距 */\r\n }\r\n \r\n /* 对粗体文字设置橙色文字和淡色背景*/\r\n b, strong {\r\n color: rgba(255,69,0,1); /* 橙红色 */;\r\n background-color: #f0f0f0; /* 淡灰色背景 */\r\n padding: 2px 4px; /* 加点内边距让背景更明显 */\r\n border-radius: 2px; /* 可选:为背景添加圆角 */\r\n }\r\n \r\n/* 标题1设计,左侧边,居中,红色背景*/\r\n h1 {\r\n color: black!important;\r\n margin-bottom: 2em;\r\n margin-right: 5px;\r\n padding: 8px 15px;\r\n letter-spacing: 2px;\r\n /* 保持文字颜色为纯白色 */\r\n border-left: 10px solid rgba(240,19,19,0.5); /* 可以根据需要调整边框颜色 */\r\n background:rgba(240,19,19, 0.25);\r\n border-radius: 5px;\r\n text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); /* 文字阴影,增强对比 */\r\n box-shadow: 1px 1px 2px rgba(51, 51, 51, 0.5); /* 盒子阴影,可根据需要调整 */\r\n text-align: center;\r\n }\r\n \r\n/* 标题2设计,左侧边,居中,绿色背景*/\r\n h2 {\r\n color: black!important;\r\n margin-bottom: 2em;\r\n margin-right: 5px;\r\n padding: 8px 15px;\r\n letter-spacing: 2px;\r\n /* 保持文字颜色为纯白色 */\r\n border-left: 10px solid rgba(102, 204, 153,0.5); /* 可以根据需要调整边框颜色 */\r\n background:rgba(102, 204, 153, 0.25);\r\n border-radius: 5px;\r\n text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); /* 文字阴影,增强对比 */\r\n box-shadow: 1px 1px 2px rgba(51, 51, 51, 0.5); /* 盒子阴影,可根据需要调整 */\r\n text-align: center;\r\n }\r\n\r\n```"},{"id":"dataview","title":"Dataview","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"dataview","description":"1 查询依据 yaml数据/metainfo 2 使用查询语言 使用下列语法创建查询语言代码块 dataview query command 3 使用内联查询 内联查询的结果只能是一个,不能查询一个列表。当前页面可以通过获得,其他页面可以通过双链语法获得 通过下列语法创建内联查询: 此博客文件名: ...","relativePath":"Tool/Note/Obsidian/dataview.md","rawContent":"# 1 查询依据\r\nyaml数据/metainfo\r\n# 2 使用查询语言\r\n\r\n使用下列语法创建查询语言代码块\r\n\r\n````text\r\n```dataview\r\n query command\r\n```\r\n````\r\n\r\n## 2.1 examples\r\n\r\n### 2.1.1 列表\r\n\r\n例如,使用以下方式我们可以给每个文件夹创建一个目录\r\n\r\n````text\r\n```dataview\r\n# 文件全路径名包含Note/Obsidian\r\nLIST WHERE contains(file.path, \"Note/Obsidian\")\r\n```\r\n````\r\n\r\n```dataview\r\nLIST WHERE contains(file.path, \"Note/Obsidian\")\r\n```\r\n\r\n# 3 使用内联查询\r\n\r\n内联查询的结果只能是一个,不能查询一个列表。当前页面可以通过`this.`获得,其他页面可以通过双链语法`[[page_name]]`获得\r\n\r\n通过下列语法创建内联查询:\r\n\r\n```text\r\n`= this.file.name` \r\n```\r\n\r\n此博客文件名:`= this.file.name`\r\n\r\n# 4 使用JavaScript API\r\n\r\n使用下列语法创建JS dataview代码块:\r\n\r\n````text\r\n```dataviewjs\r\n... js code ...\r\n```\r\n````\r\n\r\n# 5 参考链接\r\n[官方文档](https://blacksmithgu.github.io/obsidian-dataview/)\r\n"},{"id":"Excalidraw","title":"Excalidraw","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"excalidraw","description":"obsidian 是一个手工风格的白板工具。可以使用呼出命令菜单,输入excalidraw进行创作。 library 提供了很多公开的模板库可以帮助我们画出很多精美的图案。 导出 可以导出为png或svg 双链 鼠标右键选中create link,可以在excalidraw中使用双链。和在markd...","relativePath":"Tool/Note/Obsidian/Excalidraw.md","rawContent":"#obsidian \r\n[Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin)是一个手工风格的白板工具。可以使用`ctrl+p或command+p`呼出命令菜单,输入excalidraw进行创作。\r\n\r\n# library\r\n\r\n[library](https://libraries.excalidraw.com/?target=_blank&referrer=app%3A%2F%2Fobsidian.md&useHash=true&token=dR6oTmJQXvWDH2qCVAbIe&theme=dark&version=2&sort=default)提供了很多公开的模板库可以帮助我们画出很多精美的图案。\r\n\r\n# 导出\r\n可以导出为png或svg\r\n\r\n# 双链\r\n\r\n鼠标右键选中create link,可以在excalidraw中使用双链。和在markdown文档中的使用规则一样,两个中括号中是被引用的文档。同时也可以在markdown中引用excalidraw的图。![[Excalidraw使用示例]]\r\n- 按住Command并把鼠标放在图中的双链上就会显示一个小的预览图\r\n- Command+鼠标左键就可以打开图中的被引用的文档\r\n- Command+Shift+鼠标左键就可以在新页面打开文档\r\n- Command+Shift+Alt+鼠标左键就会新建一个当前文本的页面\r\n# Templater 联用\r\n可以联合使用Templater作为模板生成一些常用画图模板。"},{"id":"Image auto upload Plugin","title":"Image Auto Upload Plugin","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"image-auto-upload-plugin","description":"obsidian 使用aliyun oss作为obsidian图床 1. 下载 2. 配置oss作为图床 3. 在obsidian中粘贴图片后自动上传aliyun os","relativePath":"Tool/Note/Obsidian/Image auto upload Plugin.md","rawContent":"#obsidian \r\n使用aliyun oss作为obsidian图床\r\n\r\n1. 下载[picgo](https://molunerfinn.com/PicGo/)\r\n2. 配置oss作为图床\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202301202239089.png)\r\n\r\n3. 在obsidian中粘贴图片后自动上传aliyun os"},{"id":"Mind map","title":"Mind Map","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"mind-map","description":"obsidian 1. 使用方式 使用呼出命令行,输入通过提示补全命令 2. pin 可以将思维导图的预览面板嵌到当前笔记中。 3. copy screenshot 将svg格式mind map复制到剪切板 4. bug修改 mind map已经很久没有维护了,对于代码块支持存在bug,会导致无法生...","relativePath":"Tool/Note/Obsidian/Mind map.md","rawContent":"#obsidian \r\n# 1. 使用方式\r\n\r\n使用`command+p`呼出命令行,输入`mind map`通过提示补全命令\r\n\r\n# 2. pin\r\n\r\n可以将思维导图的预览面板嵌到当前笔记中。\r\n\r\n# 3. copy screenshot\r\n\r\n将svg格式mind map复制到剪切板\r\n\r\n# 4. bug修改\r\n\r\nmind map已经很久没有维护了,对于代码块支持存在bug,会导致无法生成,删除如下代码段,去除对代码段的解析。\r\n\r\n```js\r\nelse if (token.type === 'fence') {\r\n let result = md.renderer.render([token], md.options, {}); \r\n \r\n // Remarkable only adds className to `` but not `
`, copy it to make PrismJS style work.\r\n      const matches = result.match(//);\r\n      if (matches) result = result.replace('
', ``);\r\n      current.c.push({\r\n        t: token.type,\r\n        d: depth + 1,\r\n        v: result,\r\n        c: []\r\n      });\r\n    }\r\n```\r\n\r\n对于文章中存在不合法的双链,解析也会有问题,如使用`![[]]`讲述双链语法,会被mind map错误地认为是一个合法的双链进行解析。但是代码没有判空。\r\n```js\r\n// 原文\r\nif (linkPath.startsWith('http')) {\r\n    continue;\r\n}\r\n// 修改为如下\r\nif (typeof(linkPath) == \"undefined\" || linkPath.startsWith('http')) {\r\n    continue;\r\n}\r\n```\r\n"},{"id":"Minimal Theme setting","title":"Minimal Theme Setting","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"minimal-theme-setting","description":"obsidian 设置主题 settings -> options -> appearance里面选择主题 Style settings 自定义包括字体等各种样式 Minimal Theme setting 里面内置了一些经典的配色,可以对主题进行一些快速设置","relativePath":"Tool/Note/Obsidian/Minimal Theme setting.md","rawContent":"#obsidian \r\n# 设置主题\r\n\tsettings -> options -> appearance里面选择主题\r\n# Style settings\r\n自定义包括字体等各种样式\r\n# Minimal Theme setting\r\n里面内置了一些经典的配色,可以对主题进行一些快速设置"},{"id":"Obsidian-Tasks","title":"Obsidian Tasks","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"obsidian-tasks","description":"任务管理 常用语法 1. 今日之前(包括)已完成,done before 2. 本周截止当前日(包括)已完成,done after 3. 根据重要性排序,sort by priority reverse 示例 获取本周所有已经完成的任务,按优先级倒序排列 tasks done before done...","relativePath":"Tool/Note/Obsidian/Obsidian-Tasks.md","rawContent":"任务管理\r\n\r\n# 常用语法\r\n\r\n1. 今日之前(包括)已完成,done before <% tp.date.now(\"YYYY-MM-DD\", 1) %> \r\n2. 本周截止当前日(包括)已完成,done after <% tp.date.weekday(\"YYYY-MM-DD\", 0) %> \r\n3. 根据重要性排序,sort by priority reverse\r\n\r\n# 示例\r\n\r\n获取本周所有已经完成的任务,按优先级倒序排列\r\n````text\r\n```tasks\r\ndone before <% tp.date.now(\"YYYY-MM-DD\", 1) %> \r\ndone after <% tp.date.weekday(\"YYYY-MM-DD\", 0) %> \r\nsort by priority reverse\r\n```\r\n````\r\n"},{"id":"Obsidian总览","title":"Obsidian总览","date":"2025-09-17T13:31:12.000Z","tags":["obsidian","tool"],"readingTime":7,"slug":"obsidian总览","description":"obsidian 1. 视图 obsidian一共提供了三种视图: - preview mode:预览模式,可以编辑,显示实时预览结果,此模式下metadata不会渲染 - reading mode:阅读模式,markdown渲染后结果,不可编辑 - source mode:以纯文本形式显示mark...","relativePath":"Tool/Note/Obsidian/Obsidian总览.md","rawContent":"---\ntags:\n- obsidian\n- tool\n---\n\n#obsidian \n\n# 1. 视图\n\nobsidian一共提供了三种视图:\n\n- preview mode:预览模式,可以编辑,显示实时预览结果,此模式下metadata不会渲染\n- reading mode:阅读模式,markdown渲染后结果,不可编辑\n- source mode:以纯文本形式显示markdown\n\n以下是我的切换快捷键设置\n\n- `⌘⌥S` 切换预览和源码模式\n- `⌘E` 切换预览和阅读模式\n- 阅读和源码模式无法直接相互切换\n\n ---\n# 2. 核心功能\n\n## 2.1. 双链\n\n### 2.1.1. 语法\n- 链接到文章, `[[obsidian总览]]` \n- 链接到标题,`[[obsidian总览#语法]]` ^c9e6ba\n- 链接到文本块,`[[obsidian总览#^c9e6ba]]`\n- 链接别名,`[[obsidian总览#^c9e6ba | 链接别名]]`\n\n### 2.1.2. 查看\n按住`ctrl或command`,光标悬浮预览。\n\n[[Obsidian总览]]\n[[Obsidian总览#语法]]\n[[Obsidian总览##^c9e6ba]]\n[[Obsidian总览#^c9e6ba | 链接别名]]\n\n### 2.1.3. 引用\n使用`![[]]`可以直接将段落引用到当前页面,作为当前页面的一部分显示.\n![[Obsidian总览#语法]]\n\n### 2.1.4. 标注\n[Callouts - Obsidian Help](https://help.obsidian.md/Editing+and+formatting/Callouts#Supported+types)\n\n> [!tip]\n\n> [!bug]\n\n> [!warning]\n\n> [!info]\n\n> [!failure]\n\n> [!success]\n\n> [!question]\n\n> [!danger]\n\n> [!example]\n\n> [!unknown]\n\n> [!note]\n\n> [!todo]\n\n\n\n## 2.2. 搜索\n\n### 2.2.1. `⌘F`进行搜索当前文档\n\n### 2.2.2. `⌘⇧F`搜索整个资料库\n\n默认是搜索全部内容,也可以使用`prefix:`的形式指定范围搜索。\n\n\t1. 搜索文本内容:`content:key`\n\t2. 搜索文件名:`file:key`\n\t3. 搜索tag: `tag:#key`\n\t4. 搜索同一章节:`section:key`\n\t5. 搜索同一段落:`block:key`\n\t6. 搜索同一行:`line:(key1 key2)`\n\nobsidian还支持多个关键词组合查询,举例如下:\n\n- 文件同时包含obsidian和tool标签,`tag:#obsidian and tag:#tool`\n- 文件名包含数据库关键词,且文本中某一行同时出现了隔离和mysql关键字,`file:数据库 line:(隔离 mysql)`\n- 文件名包含数据库或者文件名包含obsidian,`file:数据库 OR file:obsidian`\n- 文件名包含数据库或者拥有obsidian标签,`file:数据库 OR tag:#database`\n- 文件包包含数据库且不包含隔离关键字,`file:数据库 - file:隔离`\n\n### 2.2.3. 搜索task\n\n- 搜索所有待办事项:`task:''`\n- 所有带有指定关键词的待办事项:`task:key`\n- 搜索未完成的待办事项:`task-todo:''`\n- 搜索已完成的待办事项:`task-done:''`\n\n### 2.2.4. 保存搜索结果\n\n使用如下语法进行查询\n\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/obsidian%E4%BF%9D%E5%AD%98%E6%9F%A5%E8%AF%A2%E7%BB%93%E6%9E%9C.png)\n\n查询结果会内嵌到当前页面中。\n\n```query\nfile:obsidian OR tag:#obsidian\n```\n\n## 2.3. 标签\n\n#obsidian\n\nobsidian添加标签有两种方式:\n\n1. 在文章任意地方可以通过 `#标签名称` 插入标签,然后可以在「标签面板」中看到所有仓库中的标签。注意标签需要在单独一行,且`#`和标签名称间不能存在空格,否则会当成标题处理。\n2. 在文章的meta区通过yaml语法插入标签,如下:\n```yaml\n---\ntags:\n- obsidian\n---\n```\n\n---\n\n# 3. 核心插件\n\n## 3.1. 幻灯片\n\n## 3.2. 日记\n\n## 3.3. canvas\n\n### 3.3.1. card\ncanvas支持以下几种card\n- text card\n- markdown笔记\n- 视频&文件\n- pdf文件\n- 网页,可以预览整个网页\n\n### 3.3.2. 拖拽文件夹\n\n![Kapture 2023-01-21 at 14.00.50.gif](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/canvas%E6%8B%96%E6%8B%BD%E6%96%87%E4%BB%B6%E5%A4%B9.gif)\n\n### 3.3.3. group\n\n同时选中多个card,右击可以新建group\n\n### 3.3.4. 嵌套\ncanvas支持嵌套使用,即在一个canvas上叠加canvas\n\n## 3.4. Template\n\nobsidian支持模板功能,可以快速插入一段模板代码。还支持动态参数,如`` 可以获取当天时间\n\n第三方插件Templater可以支持更为强大的模板功能。\n\n# 4. 三方插件\n\n| 名称                                                                                        | 简介                                  | 详情介绍                         |\n| ----------------------------------------------------------------------------------------- | ----------------------------------- | ---------------------------- |\n| [paste url into selection](https://github.com/MarkMindCkm/obsidian-markmind)              | 可以实现选中文字后,按下`command + v`即可将url链接到文 |                              |\n| [Editor Syntax Highlight](https://github.com/deathau/cm-editor-syntax-highlight-obsidian) | 代码高亮                                |                              |\n| [mind map](https://github.com/lynchjames/obsidian-mind-map)                               | 将markdown通过思维导图的方式呈现                | [[Mind map]]                 |\n| [Advanced Table](https://github.com/tgrosinger/md-advanced-tables)                        | 增强的表格工具                             | [[Advanced Table]]           |\n| [Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin)                     | 手绘风格作图工具                            | [[Excalidraw]]               |\n| File Explorer Note Count                                                                  | 显示文件夹中文件数量                          |                              |\n| Image auto upload                                                                         | 图床工具                                | [[Image auto upload Plugin]] |\n| Minimal Theme Setting                                                                     | obsidian主题设置                        | [[Minimal Theme setting]]    |\n| Quick Explorer                                                                            | obsidian窗口下方显示文件导航                  |                              |\n| pandoc                                                                                    | 将obsidian笔记导出为其他各种格式的文件             |                              |\n| [memos](https://github.com/quorafind/obsidian-memos)                                      | 备忘录,记录小灵感                           |                              |\n| dataview                                                                                  |                                     | [[dataview]]                 |\n| kanban                                                                                    |                                     |                              |\n| tasks                                                                                     |                                     |                              |\n| calender                                                                                  |                                     |                              |\n| reveal active file button                                                                 | 可以自动展开当前文件路径                        |                              |\n| better file link                                                                          | 可以将文件插入到当前page,也可以引用其他存储目录的文件       |                              |\n| [Templater](https://silentvoid13.github.io/Templater/)                                    | 支持强大的模板语法                           |                              |\n| Number Headings                                                                           | 可以自动给标题编号                           |                              |\n| Kanban                                                                                    | 可以实现类似teambition或者trello类似的看板功能     |                              |\n| Eidting toolbar                                                                           | 提供一个编辑栏,给文字加颜色,添加附件等等工具             |                              |\n| spreadsheet                                                                               | 提供类似于excel的高级表格工具                   |                              |\n| obsidian git                                                                              | 使用git同步笔记,国内使用gitee体验更好             |                              |\n\n# 5. 调试\n\n当使用obsidian插件遇到问题的时候,可以使用`⌘+⌥+i`打开调试窗口。\n\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/obsidian%E8%B0%83%E8%AF%95%E7%AA%97%E5%8F%A3.png)\n\n# 6. 发布gitpage\n\n## 6.1. excalidraw转化\n1. 设置excalidraw同步生成svg图片\n2. 图片上传阿里云,并自动将`![[]]`语法转化为`![]()`图片引用\n\n```shell\n# 以北京为例\ngit clone git@github.com:shinerio/ob_tools.git\ncd ob_tools\nexport oss_endpoint='http://oss-cn-beijing.aliyuncs.com'\nexport oss_path='obsidian/'\nexport oss_ak=your_ak\nexport oss_sk=your_sk\nexport oss_bucket=your_bucket\npython main.py --vaultpath=\"obsidian vault路径 \\\n--input=\"需要转换的文件相对于vaultpath的路径\" \\\n--outputpath=\"输入文件根路径, 输出目录将会保持和vault一样的目录结构\" \\\n--attachment=\"本地图片存储路径\"\n```"},{"id":"代理","title":"代理","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"代理","description":"ssh代理","relativePath":"Tool/Terminal/代理.md","rawContent":"ssh代理\r\n```shell\r\n$ ssh -o ProxyCommand=\"nc -X 5 -x 127.0.0.1:7890 %h %p\" root@server\r\n```"},{"id":"效率工具","title":"效率工具","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"效率工具","description":"1. 添加环境变量 2. windows命令行代理 3. git代理 也可以直接编辑/.gitconfig文件 4. windows配置beyondcompare作为gitdiff 修改.gitconfig配置文件 5. 删除重复文件","relativePath":"Tool/Windows/效率工具.md","rawContent":"# 1. 添加环境变量\n```shell\n[Environment]::SetEnvironmentVariable(\"EDITOR\", \"code --wait\", \"User\")\n```\n# 2. windows命令行代理\n\n```dos\nset http_proxy=http://username:password@proxy.domain.com:8080\nset https_proxy=https://username:password@proxy.domain.com:8080\n```\n\n# 3. git代理\n\n```dos\n# 配置代理\ngit config --global http.proxy http://username:password@proxy.domain.com:8080\ngit config --global https.proxy https://username:password@proxy.domain.com:8080\ngit config --global http.sslverify false\n\n# git config --global https.proxy http://127.0.0.1:7890\n# git config --global https.proxy http://127.0.0.1:7890\n\n# 取消代理\ngit config --global --unset http.proxy\ngit config --global --unset https.proxy\n# 指定网站代理\ngit config --global http.https://github.com.proxy http://username:password@proxy.domain.com:8080\n# 取消代理  \ngit config --global --unset http.https://github.com.proxy\n```\n也可以直接编辑~/.gitconfig文件\n# 4. windows配置beyondcompare作为gitdiff\n修改.gitconfig配置文件\n```text\n[diff] \n    tool = bc4 \n[difftool] \n    prompt = false \n[difftool \"bc4\"]\n    cmd = \"\\\"D:/Beyond Compare/bcomp.exe\\\" \\\"$LOCAL\\\" \\\"$REMOTE\\\"\" \n[merge]\n    tool = bc4 [mergetool] prompt = false \n[mergetool \"bc4\"] \n    cmd = \"\\\"D:/Beyond Compare/bcomp.exe\\\" \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$BASE\\\" \\\"$MERGED\\\"\" \n    trustExitCode = true\n```\n# 5. 删除重复文件\n```poweshell\n# 预览模式\nGet-ChildItem -Path \"D:\\document\" -File -Recurse | Where-Object {$_.BaseName -match '^.+ \\d{1,2}


    

Github Oauth

▐▛███▜▌ Claude Code v2.1.34 ▝▜█████▛▘ Opus 4.6 · Claude API ▘▘ ▝▝ C:\workspace\code\shinerio.github.io > 1. 文章详情页,点击划词后,点击使用github登录,页面直接404,需要修复 2. 底...

BGP Speaker

在传统的网络设备中,我们通常直觉地认为“一台设备 = 一个 BGP 进程 = 一个 Speaker”。但在现代网络设计和高级设备架构中:一台物理路由器可以拥有多个 BGP Speaker。 为了理解这一点,我们需要区分“物理实体”和“逻辑协议实例”。以下是几种实现“多 Speaker”的典型场景: ...

BGP路由器分类

在BGP(边界网关协议)中,路由器的分类通常根据其在自治系统(AS)中的位置以及建立邻居关系的方式来划分。 1. 按协议划分 1.1. EBGP 路由器 (External BGP) 当两台运行 BGP 的路由器处于不同的自治系统(AS)时,它们之间建立的邻居关系称为 EBGP。 - 位置: 通常位...

Awesome Tools

1. 全平台 - freefilesync,文件夹同步工具 - ChatGPT流媒体解锁检测脚本: - https://gitlab.com/fscarmen/warp解锁应用端chatgpt 2. macos 2.1. 媒体工具 - snipaste截图软件 - kap屏幕gif录制工具,对于博客...

Claude Code

1. claude code配置 1.1. ide集成 在vscode中安装claude code插件,然后在claude code命令行界面使用即可连接到vscode。claude code就可以和vscode进行交付,感知你在vscode中选中的代码、文件,claude code的修改也会在vs...

Subagents

1. 子代理是专门处理特定类型任务的 AI 助手,当Claude遇到与子代理描述相匹配的任务时,它会将任务委派给该子代理,由其独立工作并返回结果。 - 每个子代理都在自己的上下文窗口中运行 - 拥有自定义的系统提示词(System Prompt) - 特定的工具访问权限和独立的权限设置。 优势: -...

Cloudflare免费Worker

cloudflare一段时间之前推出的一项免费服务, 允许在CDN服务器上运行js脚本或wasm 截止到这篇文章写完的时候,这仍是一项长期免费服务,免费套餐为每天 100000 个请求, 大概是100个人 每人请求100次, 或0.01个人 每人请求10000000次

Quantumult X

1. 参考配置 https://raw.githubusercontent.com/limbopro/Profiles4limbo/main/full.conf 2. 规则仓库 https://github.com/blackmatrix7/iosrulescript/tree/master 3. ...

Cloud Wan竞品分析

1. 腾讯 - CCN路由同步,默认会同步所有路由表 - - VPC多条路由冲突的时候,可以支持启用、停用 1.1. 跨地域流量管理 - 单向的流量调度规则限速带宽总和不得超过带宽上限。 - 带宽上限提高后,默认规则的限速带宽不会自动调整,需手动调整 2. 阿里 - CEN路由同步,默认只会同步默认...

IBGP与EBGP区别

1. 使用场景 eBGP主要用于: - 不同运营商/组织之间交换路由,例如中国电信与中国联通的互联互通 - 企业多归属(multihoming)接入多个 ISP - IXP(互联网交换点)中各参与方之间的路由交换 iBGP 主要用于: - 在一个大型 AS 内部传递从 eBGP 学到的外部路由信息,...

BGP协议优先级

1. 核心逻辑 1.1. “大”即是好的(高优先级) Weight 和 Local Preference:这两个属性是管理员手动干预的首选。数值设置得越大,代表你越“偏好”这条路径。 1.2. “短/小”即是好的(低开销) - AS-Path 长度:这体现了 BGP 的路径矢量特性,跳数越少代表路径...

Openspec

A "change" in 是一个“承载着围绕一项工作所进行的所有思考和规划的“集合。文件夹位于,包含proposal, specs, design, tasks。 工作流程 目录结构 - - 这是最重要的目录,存储了系统当前是如何运行的完整描述。 - 按domain组织:为了防止单个文档过大,它按...

Claudemd

| 类型 | 位置 | 范围 | 版本控制 | 典型用途 | | ------ | ----------------------- | ---- | ----- | ------ | | 全局 | | 所有项目 | 不共享 | 个人编码偏好 | | 项目 | | 当前项目 | 共享给团队 | 团队规...

SDD(Spec Driven Develop)

Kiro的spec流程被设计为三个步骤:需求 (requirements.md) → 设计 (design.md) → 任务 (tasks.md)。每个工作流步骤都由一个Markdown文档表示,Kiro会引导你达成这三个步骤。 1. 需求文档 它被构建为一个需求列表 1. 每个需求代表一个“用户故...

Run Code

runcode是一个来自veadk库的内置工具,它提供了一个安全的代码执行沙箱功能。这个工具允许AI Agent运行用户请求的代码片段(主要是 Python3),并返回执行结果。该工具通过字节跳动云服务的API在远程安全环境中执行代码,并具有会话管理和身份验证功能。 1. 工作原理 1.1. 工具注...

Demo

1. summary 2. 3. 对话前,无任何相关记忆 通过对话告知喜欢滑雪 再次查询记忆库 4. 5. 部署MCP服务的时候可以选择API KEY自动生成MCP的入站身份鉴权 agentkit一直无法拉起mcp工具集,这里使用claude code本地测试 6. 环境变量参考 7. 相关链接 -...

Awscli

install 配置账号 1. ref https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

Invoke

调用需要具备以下权限

Introduce

Amazon Bedrock AgentCore 同时具备框架无关性和模型无关性,让你能够灵活、安全且大规模地部署和运行高级 AI 智能体。无论你是使用 Strands Agents、CrewAI、LangGraph、LlamaIndex 或任何其他框架构建智能体,也无论你在哪个大语言模型(LLM)...

Gateway

1. v2n agent runtime需要的short-term memory需要通过v2n连接到用户vpc中的postgresql(内存中易失)

AgentCore

Amazon Bedrock AgentCore是用于构建、部署、高效运行Agent的最先进的智能化平台,其服务包括Runtime, Gateway, Memory, Identity, Observability, Browser, Code Interpreter, Evaluation, po...

Oauth2.0

OAuth 2.0是一个关于授权(Authorization)的开放标准。它允许用户让第三方应用访问该用户在特定服务上存储的私有资源(如照片、联系人等),而无需将用户名和密码提供给第三方应用。最常见的例子就是:你使用“微信登录”或“GitHub登录”来注册一个新的网站,而不需要在该网站重新设置密码。...

记忆

1. 短期记忆 LLM的核心架构在推理时,每一轮都是独立的计算过程。因此如果不把所有对话历史都带上的话,LLM就只能针对当前问题就会回答,无法感知历史对话。 在AI中,短期记忆通常指“对话上下文”,包括: - prompt - 对话历史 - 前序大模型推理结果 - 前序工具执行结果 短期记忆有两种存...

开源模型汇总

1. | Model | Total Params | Activated Params | Context Length | | :--------------: | :---------------: | :-------------------: | :----------------: | ...

Uv

1. 设置pip源 配置环境变量 2. 设置cache位置 3. 初始化项目并指定最低python版本 4. 全局安装tool 如果安装Python包是为了在终端任何地方运行它的命令(比如 , , 或者你提到的 ),可以使用 : - 效果: 它会为这个工具创建一个隐藏的独立环境,但把它的可执行命令软...

MCP Gateway

1. ref https://www.volcengine.com/docs/86681/1844858?lang=zh

Agentkit

1. 支持协议: AgentKit智能体运行时支持A2A、MCP、标准HTTP三种通信协议 2. agent访问方式 - 公网访问:默认访问方式。 - 私网访问:选择同地域中的任意一个VPC和子网,每个可用区支持最多选择一个子网。 3. 大模型 通过api endpoint对接的是火山方案 3.1....

Huoshan

基本概念

1. 参数大小 | 缩写 | 英文全称 | 中文含义 | 数值(科学计数法) | 对应中文单位 | | ------ | ------------ | -------- | ------------- | ----------- | | M | Million | 百万 | $10^6$ | 100...

Bedrock

1. Model catalog - Amazon Bedrock Foundation Models - Amazon Bedrock Marketplace - Bedrock Custom Model Import 1.1. 区别对比 | 特性 | Foundation Models | Ma...

Awesome Tools

:pdf论文翻译,提供中英文对照。

Claude Desktop通信过程

MCP

MCP与AI Agent

AI Agent向LLM “提供” 了它能使用的工具列表,这个过程通常通过以下两种方式实现: 1. 静态工具注册(Static Tool Registration): - 在设计 AI Agent 系统时,开发者会预先定义并注册一系列可供 Agent 调用的工具。 - 每个工具都有一个清晰的 描述 ...

MCP开发原理

1. MCP Server开发 1.1. 注意事项 - 使用stdio作为transport layer的时候,不要进行任何控制台输出 1.2. 交互过程 INITIALIZE = "initialize" - 协商协议版本 - primitives支持情况,如tools、resources、pro...

MCP概念理解

1. 基本概念 MCP(Model Context Protocol 模型上下文协议) 是一个开放的标准化协议,用于在AI模型和外部数据源、工具之间建立安全、可控的连接。它定义了AI系统如何访问和利用外部上下文信息的规范。MCP就像是AI应用程序的USB-C接口,为AI模型提供了一种标准化的方式来连...

MCP配置

1. 基本定义 - 每个MCP服务器都是一个独立json对象,以服务器名称作为key - key在MCP配置文件中以及全局配置文件和项目配置文件中必须是唯一的 - 每个MCP服务器条目对象都必须具有属性 2. local mcp server - (可选):在执行之前,先将进程的工作目录切换到指定路...

开发与测试

1. tools 1.1. stdio 启动命令 inspector调试 mcp config 1.2. SSE 启动命令 inspector调试 mcp config 1.3. streamable http mcp-server仅需修改transport即可 mcp confi需要将修改为,例如...

Prompt

概念

1. Message类型 System message 在大模型内部是每次加在了用户输入的前面。在 的大模型设计的时候,有三种不同的message 类型,这三者是有明显区别的。 - System Message:对大模型的角色进行定义,并输入一些基础的指令,包括大模型的身份、一些用于提高安全性的指令...

RAG

RAG(Retrieval-Augmented-Generation, 检索增强生成)is a process that helps AI models "look things up" before they answer, like accessing my calender or the we...

Vector Database

Embeddings用数值形式的向量,在高维空间表示数据(通常是文本等非结构化的数据)。传统的关系型数据库并不适合存储和搜索这些向量表示。 向量存储库能够使用相似度算法对相似向量进行索引和快速搜索,使得应用程序能够在给定目标向量的情况下找到相关向量。 例如,在个性化聊天机器人的案例中,用户会向生成式...

AI

Agntcy

1. OASF Open Agentic Schema Framework一个基于OCI(Open Container Initiative)的可扩展数据模型,用于描述agent的属性并确保agent的唯一标识。OASF支持描述A2A代理和MCP服务器,并且可以扩展以支持其他常用格式,例如Copil...

Claude Skills

1. 原理 claude skill是一类模块化能力组件,用于拓展 Claude的功能边界。每项技能都封装了: 1. 元数据 2. 指令说明 3. 可选配套资源(脚本、模板) Claude Agent Skills的设计哲学在于模块化与按需加载,旨在解决传统代理系统中常见的上下文冗余、性能衰减以及操...

CrewAI

1. install https://docs.crewai.com/en/installation

执行计划

1. explain和explain anayze 和生成的执行计划通常是一致的,但并不能保证完全一致。 1.1. 核心功能对比 | 对比维度 | EXPLAIN | EXPLAIN ANALYZE | | -------- | ------------- | --------------- | |...

Excalidraw使用示例

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements %%>>>text element-link:Excalidraw<<<%%shinerio's blog ^0js7...

锥型NAT和对称型NAT

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== %% Drawing %%

Mysql可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

Mysql当前读下幻读问题

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Excalidraw Data Text Elements 事务A ^skXrctjq 事务B ^eJoYKov2 insert into ord...

Mysql当前读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^yULSUGNg 事务B ^RjMMNDeM select from orders where 10 sel...

Mysql快照读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^b7TiLH2p 事务B ^bMLs1cuE select from orders; ^nEabCTcv i...

Postgresql不可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

A2A

协议是一项开放标准,旨在解决人工智能快速发展领域中的一个基本挑战:由不同团队构建、使用不同技术且归不同组织所有的AI Agents,如何有效通信与协作? 随着人工智能代理变得更加专业化和强大,它们在复杂任务上协同工作的需求也日益增长。想象一下,用户让其主要的人工智能助手规划一次国际旅行。这一个请求可...

AI Agent

AI Agent综述(Chatbot + Workflow + AI Agent + Function Call + MCP)

1. LLM E.G. Chat GPT, Google Gemini and claude. 1.1. Work Mechanism Human provide an input and the LLM responds with an output. > input(prompt) -> LLM...

设计模型(Work Flow & Agent)

1. 工作流模式(Predefined Workflow) 1.1. 流水线 流水线是最简单直接的工作流编排,通过编排一个顺序处理的流程,让模型逐步执行和推理。 场景示例: 文档处理系统:文档上传 → 格式转换 → 内容提取 → 语义分析 → 结果存储 1.2. 路由分发 路由分发是将输入分类(LL...

AI Code

AI Platform

Aws

Dify

1. chatbot - 核心就是对话 - 支持自定义System prompt - System prompt和Conversation opener支持jianjia变量渲染 - 支持rag 输入变量后,进行对话 2. Agent Agent相比chatbot增加了工具调用的能力 2.1. Co...

字典树(Trie)

1. 单级字典树 字典树最基础的应用——查找一个字符串是否在「字典」中出现过,也可以用来做最长前缀匹配。 如下图,每个路径代表一个字母,每个节点存储以该节点结尾的字符串是否存在,构建如下的一个字典树。 1-4-8-12,且12节点记录值为true,则代表存在这样的路径的字符串,即存在caa字符串。 ...

时间轮

时间轮(Timing Wheel)是George Varghese和Tony Lauck在1996年的论文实现的,是一种实现延迟功能(定时器)的精妙的高级算法,其算法应用范围非常广泛它在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一,在Java开发过程中常用的Dubbo、Net...

Dapr(Distributed Application Runtime)

dapr重点落在了runtime上,runtime是一个抽象概念,提供了运行时的实现,不需要开发人员操心,比如Java的runtime环境就是jvm。核心思想是模块化,通过sidecar的方式实现,然后通过本地rpc或者http调用。 1. Multi Runtime 分布式应用的需求: - 生命周...

Cloud Network

EIP分类

1. 全动态BGP(多线EIP) 云服务提供商的公网IP地址通过BGP与多个运营线直连的链路播报给多个运营商。BGP类型的带宽具备动态路由收敛能力,可靠性和抗DDos能力好,但价格相对静态BGP来说较贵。云服务提供商可以根据设定的寻路协议实时自动优化网络结构,以保持客户使用的网络持续稳定、高效。 2...

ER的作用

使用peering等方式构建的网络结构是Full Mesh,而企业路由器的网络结构是中心辐射型(星型拓扑)。

Aliyun LB介绍

1. ALB - 每个可用区占用三个ip,一个vip,两个local ip - 每个vip可以独立绑定eip - 。ALB实例的杭州可用区H发生故障时,ALB能够在短时间内停用该可用区,并继续使用其他启用的可用区提供服务。 - ALB默认开启跨AZ负载均衡,即ALB在同地域跨可用区的后端服务之间分配...

AWS LB介绍

应用程序负载均衡器(ALB)、网络负载均衡器(NLB)和网关负载均衡器(GLB)是云中使用的三类负载均衡器。 为了重定向应用程序流量 - ALB 会检查请求的内容,例如 HTTP 标头或 SSL 会话 ID - NLB 会检查 IP 地址和其他网络信息,以最佳方式重定向流量 - GLB 充当透明的网...

HW LB介绍

1. LB实例 - 可用区:选择部署的可用区列表,可用区越多,价格越贵 - 应用型和网络型:可组合选择购买应用型和网络型,组合不同,收费不同 - 网络配置: - 所属VPC - 网络类型:ipv4、ipv6,可组合选择,或都不选(只提供公网接入);选择ipv4后,可提供私网IP接入的私网负载均衡;选...

Nginx使用

1. nginx常用命令 Nginx的命令在控制台中输入就可以看到完整的命令,这里列举几个常用的命令: - nginx -s reload 向主进程发送信号,重新加载配置文件,热重启 - nginx -s reopen 重启 Nginx - nginx -s stop 快速关闭 - nginx -s...

Nginx架构与原理

1nginx默认的启动方式是多进程的方式,nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。worker的数量可以通过配置文件中的配置项控制,一般建议设置与机器cpu核数相当。 Nginx采用了事件驱动的模型,期望worke...

Aws

aws的nat网关作为vpc的一个特性,创NAT网关的时候可以选择公网还是私网。流量经过NAT网关,源地址都会转成网关的私网地址,公网NAT后面会经过internate gateway,然后将私网地址转换成公网地址。 1. 公网NAT 1.1. 性能 - 默认5 Gbps,最高100 Gbps - ...

AZURE

1. 公网NAT 只支持SNAT,不支持DNAT 1.1. 性能 - 50 Gbps - 100W - 5M PPS 1.2. other 1.2.1. 支持自定义TCP超时时间 1.2.2. 支持一组地址连续的公网地址池 支持16个EIP或/28大小的公共EIP前缀 1.2.3. 分布式VM提供服...

GCP

google的NAT网关比较特别,不是一个独立的网关设备,而是做在源计算节点上的源端NAT。Cloud NAT 是一种没有代管式中间代理的软件定义解决方案,采用,具有高可靠性、高性能和高可伸缩性。 1. 公网NAT Public NAT 网关会为使用网关创建与互联网的出站连接的每个虚拟机分配一组外部...

华为云

1. 竞争优势 不需要使用peering/er等实现跨vpc访问的能力,私网nat天然支持跨vpc网络访问。

腾讯云

1. 公网NAT 1.1. 性能 - 默认限速5 Gbps,最大可支持50 Gbps - 并发连接200万 - 10万新建 - 不占用vpc用户的私网ip 1.2. 配额 1.2.1. 网关 - 一个VPC支持3个NAT网关 - 每个NAT网关绑定最多10个EIP 1.2.2. SNAT - 单网关...

阿里云

1. 公网NAT 2. 特性 - 支持一键全通 - snat只允许修改eip - - 支持按量计费和带宽包 2.1.1. NIS(Network Intelligence Service) 支持实例级别的一键诊断,可以检测实例的配置与运行状态,并能根据诊断的异常项提供智能修复建议。诊断内容主要包括:...

Aws HyperPlane

- top完成所有包转发和包处理,一旦网络连接建立了,转发只在Top层完成 - Flow Master记录网络连接,充当Decider的缓存 - Decider实现网络逻辑,对于NAT来说就是进行会话分配 Flow Master可以主主扩展,利用率可以做得比较高,而Decider利用率相对来说较低。

Dpdk

Ovs

现在OpenVSwitch主要由三个部分组成: - ovsdb-server:OpenFlow本身被设计成网络数据包的一种处理流程,它没有考虑软件交换机的配置,例如配置QoS,关联SDN控制器等。ovsdb-server是OpenVSwitch对于OpenFlow实现的补充,它作为OpenVSwit...

发展历史

1. 转发平台发展 - 以openvswitch为代表的第一代内核态软件转发平台 - 以dpdk为代表的第二代内核态软件转发平台 - 以P4或NP为代表的第三代硬件转发平台。 第一代发展到第二代解决了内核态和用户态上下文切换代价较高的问题; 第二代发展到第三代解决了软件转发平台性能不足的问题。 第一...

云网络诞生

1. 产业竞争核心 - 芯片 - 操作系统 - 应用 2. 转发能力发展路线 虚拟交换机发展从10Gbit/s(以原始openvswitch为代表的内核态转发,使用 kernel 作为 datapath) -> 25Gbit/s(以dpdk技术为依托的内核态转发,使用用户空间作为 datapath)...

1. Vscode配置C++开发环境

1. 插件安装 C/C++ 2. 安装gcc 3. 配置C++环境 C++环境需要.vscode 文件夹下的以下三个文件共同定义 - ccppproperties.json :对C/C++扩展的设置。 - tasks.json :定义如何生成可执行文件。 - launch.json :定义如何调试可...

2. 基本语法

1. 程序入口 在 C++ 标准中, 函数是程序启动后调用的第一个用户定义函数,它有两种标准的定义形式: 1. :这种形式表示 函数不接受任何命令行参数。它向调用者返回一个整数值,用于表示程序的退出状态。通常,返回值 表示程序正常结束,非零值表示程序出现异常或错误。 2. :这种形式允许程序接收命令...

3. 命名空间

命名空间(namespace)是 C++ 中一项重要的特性,它可以将全局作用域划分为不同的部分,从而避免不同库或者不同模块之间的命名冲突。标准 C++ 库中的所有标识符(像类、函数、对象等)都被定义在了 命名空间里。 1. using namespace std 在没有使用 时,若要使用标准库中的标...

4. 变量定义与内存空间分配

C++ 中定义变量时,即使不进行初始化,内存空间通常也会被分配,不过不同类型的变量在分配内存空间的时机和默认初始值方面存在差异。 1. 局部变量(自动存储期) 局部变量一般定义在函数内部或者代码块内部,具有自动存储期。当程序执行到定义局部变量的语句时,会立即为其分配内存空间,但不会自动初始化,该内存...

5. String字符串

c++包含两种风格的字符串 - C 风格字符串 - C++ 引入的 string 类类型 1. char类型 C风格的字符串起源于C语言,并在C++中继续得到支持。字符串实际上是使用null字符\0终止的一维字符数组。因此,一个以null结尾的字符串,包含了组成字符串的字符。 2. 字符串函数 | ...

Code

Go Env

1. GOROOT与GOPATH - 环境变量表示 Go 语言的安装目录。在中,的默认值是,而在或中的默认值是,如果将Go安装在其他目录中,而需要将GOROOT的值修改为对应的目录。另外,则包含Go为我们提供的工具链,因此,应该将配置到环境变量 PATH 中,方便我们在全局中使用 Go 工具链。 -...

Go Mod

从 Go 1.11开始,Go引入了Go Modules作为官方的依赖管理解决方案。在Go Modules模式下,依赖项会被下载到目录下,命令默认会将可执行文件安装到目录下。文件位于项目根目录下,用于记录项目的模块信息和依赖项及其版本,一个典型的例子如下: 1. go mod命令 2. module ...

Go

Go命令行参数

- os.Args变量是一个字符串(string)的切片(slice) - os.Args的第一个元素,os.Args[0], 是命令本身的名字;其它的元素则是程序启动时传给它的参数

Channel

1. 声明和初始化 1.1. 仅声明(未初始化) 声明一个 int 类型的 channel,但未初始chnilpanicmake()make()ch := make(chan int)chch := make(chan int, 3)3varvar ch chan int = make(chan i...

Context

go1.7加入了一个新的标准库context,用来简化对于处理单个请求的多个goroutine之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个API调用。 fff88f">当一个上下文被取消时,它派生的所有上下文也被取消。当context生命周期结束时,可以从管道接收消息,子协...

Defer

1. 关键字 defer 用于注册延迟调用。 2. 这些调用直到return前才被执。因此,可以用来做资源清理。 3. 多个defer语句,按先进后出的方式执行。 4. defer语句中的变量,在defer声明时就决定了,语句中的fff88f">函数调用参数会在语句执行时立即求值,并保存下来。这意味...

Goto与Label

Go语言也支持label(标签)语法:分别是和 、。 - break label可以跳出label层级循环 - continue label可以从label继续下一次循环 - goto可以无条件的跳转执行的位置,但是不能跨函数,需要配合标签使用 执行结果 输出结果

函数

1. 结构体返回值 在Go语言中,函数返回结构体时,接收它的变量类型可以是值类型或指针类型 1.1. 使用值类型接收值类型返回值 - 结构体较小(如 2-3 个字段的结构体),直接复制不会有明显性能影响。 - 结构体的数据不需要在外部修改,返回值是独立的副本。 输出如下 1.2. 使用指针类型接收指...

变量赋值&结构访问

1. 取地址与解引用 是取地址符号,放到一个变量前使用就会返回相应变量的内存地址。如 是指针运算符,一个指针变量指向了一个值的内存地址。放到一个变量前可以对指针解引用,获取指针指向的实际变量。 输出如下 2. 变量赋值 go中直接对结构体进行复制,会进行值拷贝。 结构体的指针属性也会拷贝,但指针指向...

基本语法

基础

1. 变量初始化 go中使用var声明的变量会自动初始化,如下是等价的 2. array 1. 数组:是同一种数据类型的固定长度的序列。 2. 数组定义:,比如:,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。 3. 长度是数组类型的一部分,因此,和是不同的类型。 4. 数组可以通过...

并发

异常处理

在Go语言中,和都是用来处理错误情况的,但是它们的使用场景和应用场合是不同的。 1. panic 更适用于程序出现不可恢复的错误情况,例如违反了一些重要的前提条件、发生了一些严重错误等。一般来说,如果程序出现了,就意味着程序已经处于一个不可控的状态,无法继续执行下去。会导致程序立即终止并打印出错误堆...

循环与Select

1. select 语句会从上到下依次检查每个 的通信操作语句,每个case必须是一个通信操作,要么接收,要么发送。 - 如果发现某个 的通信操作可以立即执行,就会执行该 语句块并跳出 代码块。 - 如果多个 均可执行,则会随机选择一个执行。 - 如果没有任何一个 可以执行,则会执行 语句块(如果存...

类型断言

类型断言提供了访问接口值底层具体值的方式。 该语句断言接口值 保存了具体类型 ,并将其底层类型为 的值赋予变量 。若 并未保存 类型的值,该语句就会触发一个 panic。 为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。 t, ok := ...

结构体

常用Tool

1. golps golps 是LSP(Language Server Protocol)的一个语言端(Server)实现,是针对 Go 语言的LSP实现。定义了在编辑器或IDE中与语言服务器之间使用的协议,该语言服务器提供诸如自动完成,转到定义,查找所有引用等语言功能。语言服务器索引格式(LSIF...

Kafka

Redis

1. simple example 2. redis pool

Web

测试

Golang单元测试对文件名和方法名,参数都有很严格的要求。 1. 文件名必须以xxtest.go命名 2. 方法参数必须 3. 使用go test执行单元测试 在文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。 | 类型 | 格式 | 作用 | | ---- | ----------...

Benchmark

Go语言标准库内置的testing测试框架提供了基准测试(benchmark)的能力,能让我们很容易地对某一段代码进行性能测试。 本文中会对斐波那契数列进行基准测试 创建一个基准测试用例 - Benchmark测试文件必须以filename加结尾 - 方法名必须以开头 - 测试参数必须为 1. 执行...

Go性能分析

golang中性能调试优化的方法包括: - benchmark:基准测试,对特定代码的运行时间和内存信息等进行测试 - profiling: 程序分析,程序的运行画像,在程序执行期间,通过采样收集的数据对程序进行分析 - Trace:跟踪,在程序执行期间,通过采集发生的事件数据对程序进行分析 > [...

Pprof

1. 使用方式 必须在代码里引入才能使用,不像Java里jdk工具包中的 、、、、 工具可以单独使用。可以从以下两个包中引入 Golang pprof的使用方式主要有两种 1. 在程序中通过http接口的方式暴露相应的pprof的采集控制界面,需要依赖, 使用 包来进行封装。 2. 可以用来产生du...

程序调优

进程、线程与协程

1. go中线程的数量 Go 使用Goroutine 调度器 (Scheduler) 来管理Goroutine的执行。调度器的核心概念如下 1.1. GMP模型 goalng采用特有的GMP模型。 1. G(Goroutine):指的是 Go 代码中的 Goroutine。 2. M(machine...

Go面向对象

1. 自定义数据类型 增强代码可读性 2. 方法接收器 只有自定义类型(或内置类型)才能够绑定方法,从而获得面向对象的特性。例如,为 绑定 方法。,使得User对象实现了Name方法。 在Go语言中,接口的实现是隐式的,也就是说只要一个自定义类型实现了接口中声明的所有方法,那么它就被认为实现了该接口...

反射

1. 任意类型 在 Go 语言中, 常被称为“空接口”,它的确能承载fff88f">任意类型的值,但这种“万能”特性背后存在明显的权衡和局限性。也可以用关键词替代, go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息 对于这个例子, 的类型信息是 ,数据信息是 ,这两部分信息都是存储...

泛型

1. 泛型 我们知道,函数的 形参(parameter) 只是类似占位符的东西并没有具体的值,只有我们调用函数传入实参(argument) 之后才有具体的值。那么,如果我们将形参 实参这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,问题就迎刃而解:在这里我们将其称之为 类型形参(typ...

Http连接池

1. 长连接 长连接利用keep-alive技术实现,能在多次 HTTP 之间重用同一个 TCP 连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等)。 然而长连接并非没有弊端,天下没有免费的午餐,如果客户端在接收完所有的信息之后还没有关闭连接,则服务端相应的资...

Java

Java9 Java17新特性

1. 集合 增加 了 List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。 2. 私有方法 java8中允许接口有默认方法,java9中允许接口有默认私有方法 3. Optioal的ifPresentOrElse 4. 类型推断 类型...

Jni

会在指定的所有目录下查找名为 libnativememoryutils.so(Linux)或 nativememoryutils.dll(Windows)的文件。 > [!note] > 在 Linux 下,文件名必须是 libnativememoryutils.so,不能省略 lib 前缀。 1....

Mtls实现

1. 生成证书和密钥库 1.1. openssl配置 openssl中可以通过--config指定完整的配置文件,包含所有配置段(sections),用于生成包含扩展字段的证书签名请求(CSR)或自签名证书。 1.2. 生成客户端证书和服务端证书 2. 本地安装ca证书和客户端证书 windows可...

Velocity

1. 新建module-a 引入maven依赖 自定义Annotation 继承AbstractProcessor,实现自定义Processor 2. 新建module-b 添加依赖module-A 类填写注解,以生成编译后的class。可以添加些属性,用于编译class时使用。也可以给多个类添加注...

内存管理

1. 总结 java应用的内存主要分为三块 1. jvm管理的堆内存 2. jvm管理的非堆内存 3. 非jvm管理的内存 1.1. JVM管理的堆内存(Heap Memory) 存储对象实例,是 Java 内存管理的核心区域,由所有线程共享。 - 新生代(Young Generation):存放新...

虚拟线程

1. 概念 1.1. 平台线程 我们常用的Java线程与系统内核线程是一一对应的,系统内核的线程调度程序负责调度 Java线程。为了增加应用程序的性能,我们会增加越来越多的Java线程,而由于多种因素,平台线程的数量受到了很大的约束。 - 资源有限导致系统线程总量有限,进而导致与系统线程一一对应的平...

Grpc

1. 工具推荐 - apifox:rpc客户端,可以导入proto文件,自动生成rpc客户端并重试 2. 通信过程 3. 抓包 4. protocol buffers Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数...

Rpc

4+1视图

| 视图类型 | 描述 | | :-------------- | :------------------------------------------------------------------------------------------ | | 逻辑视图 | 逻辑视图面向系统逻辑分析和...

DDD

DDD理论与实践

1. 什么是DDD DDD是一种处理高度复杂领域的ff0000">设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD不是架构,而是一种架构ff0000">设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域...

核心概念

通用模型 NAT网关模型 1. 领域 汉语词典的解释:领域是从事一种专门活动或事业的范围 、部类或部门。 百度百科的解释:领域具体指一种特定的范围或区域。 两个解释有一个共同点——范围,领域的核心重点落在ff0000">”域”字上,用来确定范围的,fff88f">范围即边界 ,这也是DDD在设计中不...

Design

代码坏味道

1. 重复代码 2. 长函数 3. large class 4. Divergent Change(发散式变化) 对程序进行维护时, 如果添加修改组件, 要同时修改一个类中的多个方法,那么这就是 Divergent Change。举个汽车的例子,某个汽车厂商生产三种品牌的汽车:BMW、Benz和La...

设计原则与典型架构设计模型

架构的设计本质都是为了高内聚、低耦合 1. SOLID原则 1.1. S单一职责原则(single responsibility) 一个class应该只做一件事,一个class应该只有一个变化的原因,核心是ff0000">功能特性解耦和ff0000">高内聚性。避免一个类承担两个特性,修改A特性的时...

1. 为什么要Dpdk(Data Plane Development Kit)

1. 基于OS内核转发的劣势 1.1. 数据路径长,协议栈处理开销大 - 内核路径: 数据包需经过内核协议栈(如TCP/IP栈)的多层处理(链路层→网络层→传输层→应用层),涉及大量内存拷贝、校验和计算、上下文切换等操作。 - DPDK优化: 直接绕过内核,在用户空间处理数据包,省去协议栈的逐层解析...

ConfigMap

1. 创建 1.1. 通过kubectl命令行创建 1.1.1. --from-file参数从文件中进行创建 其中key=是可选的,默认key就是文件名,通过key=可以指定key。 1.1.2. --from-file参数从目录中进行创建 目录下每个配置文件名都被设置为key,文件的内容设成为va...

Ingress Gateway

在Kubernetes的服务网格架构中,Ingress Gateway是通过Envoy实现的。在Istio中,Ingress Gateway 是用于管理进入服务网格的流量的组件,它基于 Envoy 代理构建。Envoy 是一个高性能的代理,专门设计用于处理服务到服务的通信,支持动态服务发现、负载均衡...

Kube Proxy和Istio Envoy

- 每个节点安装了一个kube-proxy - 每个pod以sidecar的形式部署一个envoy - kube-proxy拦截的是进出kubernetes节点的流量,envoy拦截的是进出pod的流量。 - istio作为服务网格的一种实现,本质上提供了应用间的流量、安全管理和可观察性。 1. k...

Kubernetes 核心概念

kubernetes中的基本概念和术语大多是围绕资源对象来说的,而资源对象在总体上可以分为以下两类: (1)某种资源的对象,例如节点(Node),Pod,服务(Service),存储卷(Volume) (2)与资源对象相关的事物与动作,例如标签(Label)、注解(Annotation)、命名空间(...

Pod调度

k8s Master上的Scheduler服务负责实现Pod调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通过我们无法知道Pod最终会被调度到哪个节点上。 1. NodeSelector 可以实现将Pod调度到一些指定的Node上,可以...

Service

一个service对外暴露一个,client可以访问这个来实现负载均衡访问后端Pod。一个service对应的“后端”由pod的ip和端口号组成,这在kubernetes中称为endpoint。一组endpoints形如,其中和都是podIp。

Postgresql DML

1. 常用运维sql

性能优化

1. 查看数据库CPU有没有冲高,一般是有慢SQL 2. 查看数据库I/O有没有冲高,如果有一般是用了外部排序 3. 优化外部排序有两个思路,一个是添加索引,避免临时排序;第二个是限制数据规模,尽量保证在内存中完成排序 4. 先联表后排序,索引会无法利用;要做利用索引做子查询排序,然后再联表 5. ...

数据库关键配置

1. socketTimeout 未配置socketTimeout时,应用不会超时,会永远等待。当出现网络隔离故障时,服务端访问数据库的连接会hang住,导致需要等到tcp重传失败或者keepalive失败才会断开连接,故障时间会非常长。 数据库连接池中的连接可能处于如下两种状态 - 连接处于繁忙状...

数据库理论

1. 三大范式 - 第一范式(1NF) 保证列的原子性,一张表的某个列不能有多个值,不能出现重复列。实际操作中我们可能会违反这个约束,如将列值存成一个json字典,但是我们一定要保证这些值至少是一起读或写的,不存在经常部分更新的场景。 - 第二范式(2NF): 取消部分依赖,表中的每个字段都与主键相...

Etcd

Etcd常见用法

1. 数据put和get 1.1. 设置key和value 1.2. 获取指定key 1.3. 获取前缀下所有key 2. 数据过期与续约 2.1. 创建租约并设置 TTL(Time To Live) 创建一个 TTL 为 60 秒的租约: 该命令会返回一个租约ID,例如 2.2. 查看所有leas...

Etcd架构

etcd 集群通过Raft算法实现了 “动态主从 + 分布式共识” 的架构,确保数据强一致性和高可用性。etcd 集群在同一时刻只能有一个主节点(Leader),且写操作必须由该主节点处理,但读操作可以从任意节点执行。etcd集群中的节点在正常运行时通过Raft算法动态选举 “领导者(Leader)...

Watch使用与原理

Watch机制是etcd的核心功能之一,它允许客户端监听指定键或前缀的变化,实现配置变更的实时通知。默认情况下,watch是从ff0000">最新版本开始监听的。 1. 使用方式 1.1. 基本使用 添加或修改key etcd提供了多种方式使用Watch功能 1.1.1. 命令行方式 1.1.2. ...

数据存储

1. 数据存储格式 etcd的数据存储格式主要是基于键值对的形式。 - 键:是一个唯一的字符串,用于标识存储的数据,类似于字典中的键。例如,在一个配置管理系统中,键可能是某个服务的名称加上配置项的名称,如。 - 值:可以是任意类型的数据,如字符串、整数、JSON 对象等,这取决于具体的应用场景。例如...

Kafka Stream

1. 流式计算和批计算 1.1. 流式计算 流式计算是持续地从数据源获取数据,并实时地对数据进行处理和分析。 1.1.1. 特点 - 实时性强,能够在数据到达时立即进行处理。 - 数据无边界,是持续产生的 - 计算模型有状态,需要维护中间状态,一般用增量计算代替全量计算 - 时延敏感,需要低延迟处理...

Kafka架构

1. topic kafka将消息以topic为单位进行分类,一个topic就是一个逻辑队列。类比于数据库中的分库。 2. partition 为了实现扩展性,提高并发能力,kafka将一个逻辑队列(topic)划分为多个partition,每个partition保存一个topic中的部分数据,每一...

Kafka核心配置项

这里我们以网络服务用的最多的kafka组件为例,来介绍下其实现原理,已经如何在知晓原理的情况下用好kafka。 1. producer - : 控制producer端开启幂等和事务能力。开启幂等后能够保证消息不重复。 - 为生产者提供了一个唯一的事务标识符。通过这个标识符,Kafka 能够跟踪和管理...

Kafka生产消息

1. kafka获取partition对应节点 通过元数据获取 - Kafka producer 在启动时会向 Kafka 集群中的任意一个 broker(通常是配置中的 bootstrap.servers 列表中的一个)发送元数据请求(Metadata Request)。这个请求用于获取集群的元数...

Kafka高可靠

1. 生产可靠 为保证Producer发送的数据,能可靠地发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送ACK(ACKnowledge 确认收到)。如果Producer收到ACK,就会进行下一轮的发送,否则重新发送数据。 1....

Kafka高性能读写

核心: - 顺序读写 - page cache,避免每次读取磁盘。也正是因为顺序读写,避免了大量page cache miss,可以充分利用page cache。 - 零拷贝。 1. 存储结构 一个topic下多个partition,每个partition使用一个文件夹存储。partition命名规...

不同消息队列对比

- RocketMQ 的存储结构相对较为灵活,可以针对不同的场景进行优化。它采用了单一的 commitlog 文件来顺序存储所有消息,然后通过索引文件快速定位特定topic的消息。这种设计使得在处理大量 topic 时,存储资源的分配更加高效。每个topic都对应独立索引文件,加上page cach...

云时代的消息中间件

消息队列核心

- 为什么需要消息队列 - 消息队列的核心设计和使用 - 深入消息队列高性能原理 cap原则 1. 为什么需用消息队列 1.1. 业务解耦 业务解耦,代码解耦。现在的云网络服务大都遵循SDN设计思想,将服务拆分成管控转三部分,不同组件负责承载不同的业务,其解耦方式之一就是通过消息队列。很多消息队列都...

Middleware

Redis

Redis关键参数

1. go-redis 在go- redis中,连接设置是通过 Options 结构体来管理的。 1.1. 单机模式 - Network - 网络类型,可以是 tcp 或 unix,默认值为 tcp。 - Addr - Redis 服务器的 host:port 地址。 - Dialer - 创建新网...

Redis命令

1. redis支持数据类型 - 字符串 - hash(key-value) - list(有序列表) - set(无序唯一集合) - zset(有序唯一优先级队列,每个元素关联一个分数,按从小到大排列) - Bitmap - HyperLogLog,占用内存很小(12kb)的情况,可以用于估算接近...

Redis批处理

1. pipeline Redis执行一条命令需要经历以下过程:、、、。由于Redis本身是基于协议(停等机制)的,虽然Redis已经提供了像 、 这种批量的命令,但是如果某些操作根本就不支持或没有批量的操作或者需要连续执行好几个不同命令,那我们就只能一条一条地执行命令,每执行一条命令都要消耗请求与...

Redis高可用

1. master和slave数据复制机制 在Redis集群模式下,Master和Slave之间默认采用异步复制 - 客户端向Master写入数据后,Master立即返回成功响应,无需等待Slave确认。 - 数据随后异步复制到 Slave 节点。 这种设计的目的是保证高性能和可用性,但存在数据丢失...

代码示例

发布订阅模式

Redis发布订阅简介 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis的SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。 作为例子, 下...

Zookeeper

分布式锁

1. 分布式公平锁 ZooKeeper的临时顺序节点,天生就有一副实现分布式锁的胚子。 1. ZooKeeper的每一个节点,都是一个天然的顺序发号器。 在每一个节点下面创建临时顺序节点(EPHEMERAL\SEQUENTIAL)类型,新的子节点后面,会加上一个次序编号,而这个生成的次序编号,是上一...

基本概念

1. zookeeper数据存储 ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如 /hbase/master,其中 hbase 和 master 都是 ZNode。每个 ZNode 上都会保存自己的数据内容,同时...

DCN

DCN架构演进

1. 服务与网络三大耦合问题 - 交换机堆叠,存在单点风险和运维困难问题 - 物理网络大二层隧道,引入更大故障域 - 硬件墙的引入限制整体云业务规模,硬件强性能不行,无法横向扩展 2. 名词解释 NSA(Network Service Area) - NWS(Network Service): 网络...

交换机堆叠

1. 背景 主机连接单个交换机,存在单点故障问题(单个交换机故障),解决tor交换机单点故障的思路有两个。 - 上层服务(计算、网络)保证高可用,当一个tor下主机故障时,可以快速在另一台tor下拉起新主机 - 两台tor交换机堆叠,保证tor交换机的高可靠 2. 概念 交换机堆叠一般是指被背板堆叠...

大二层架构的问题

1. 历史背景 网关在汇聚上的原因是因为openstack平面约束,要求资源池里面的节点必须在一个二层上。 管理服务区解大二层原因是管理区容灾1.0方案依赖vip在az间漂移,管理区汇聚下就是一个二层。同时,az之间还需要在两个az的汇聚交换机上通过硬件vxlan打一个二层隧道。 2. 大二层的问题...

(七)Linux下实现NAT

在NAT Overview一文中,我们介绍了在linux下通过iptables实现NAT功能的命令及原理。本文以容器为例,进行几个简单的实验来进一步了解linux下NAT的实现过程和应用。 1. nat在容器中的应用 1.1. SNAT linux下一个最典型的NAT应用就是docker容器借助宿主...

(九)高性能NAT

(五)NAT ALG

--- title: NAT ALG date: 2023-02-01 categories: - network tags: - network - nat --- 能够对特定的应用层协议进行转换,在对这些特定的应用层协议进行转换过程中,通过的会话信息来改变封装在报文载荷中的和端口信息,最终实现下...

(八)高可靠NAT

1. 双机冷备 2. 双机热备 双机热备可以极大地提升NAT服务的可靠性和承载能力,但是两台NAT设备上的NAT配置也需要完全相同。这样就会出现一个问题:如果两个NAT设备分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了优先级的概念。 在双机热备的环境...

(六)路由器配置NAT

本章节主要以cisco路由器配置为例,使用GNS3仿真平台搭建一个最简单的实验环境进行验证。 1. 常用命令(cisco) 首先介绍本实验可能用到的一些命令,先有个概念,后面再在实验中熟悉并掌握。 1.1. VPC 1.2. 路由器 2. 环境 2.1. 配置PC 2.1.1. PC1 2.1.2....

(十)NAT穿透

NAT高可靠

1. 双机热备 双机热备对于防火墙来说是一个必不可少的功能,两台防火墙上的NAT配置也需要完全相同,这样就会出现一个问题:如果两个防火墙分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了地址池优先级的概念。 在双机热备的环境中,如果地址池被配置为高优先级...

流日志

1. 格式 1.1. 网络信息7元组 internalip,internalport,externalip,externalport,transitip,transitport,protocol 1.2. 流量数据 packetnum,bytesize 1.3. 时间信息 starttime,end...

Network

IP分片报文和TCP分段报文

分组可以发生在传输层和网络层,传输层中的TCP会分段,网络层中的IP会分片。IP层的分片更多的是为传输层的UDP服务的,由于TCP自己会避免IP的分片,所以使用TCP传输在IP层都不会发生分片的现象。我们在学习TCP/IP协议时都知道,TCP报文段如果很长的话,会在发送时发生分段,在接受时进行重组,...

SCTP

SCTP ( Stream Control Transmission Protocol ),即流媒体控制传输协议,是一种可靠的基于无连接数据包网络如IP网络之上传输协议。他被设计用来在IP网络上传输PSTN在窄带信令消息,同时也能支持宽带信令消息的传输。 SCTP可以看作OSI层次结构中的传输层,它...

Tcp KeepAlive

1. 起源 连接有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接。长连接环境下,双方建立交互的连接并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现: 1. 主机突然掉电、死机、异常重启 2. 中间路由...

Tcp重传

1. tcp超时重传 重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的确认应答报文,就会重发该数据,也就是我们常说的超时重传。 TCP 会在以下两种情况发生超时重传: - 数据包丢失 - 确认应答丢失 1.1. RTT与RTO - RTT(Roud Tri...

Nat封装Pp协议

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements client ^pdsoIL0z NAT ^lEtOOR5s Server ^7HyKnicB SYN seq0 ^3...

源地址透传

1. TOA(TCP Option Address) TOA将源地址放在字段中。option字段最长40字节,每个选项由三部分组成:op-kind、op-length、op-data,我们最常见的MSS字段就是在option里。目前option使用的op-kind并不多,我们只需要构建一个不冲突的o...

DHCP协议

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,统一使用两个IANA分配的端口:67(服务器端),68(客户端),DHCP客户端使用的源端口号为68,目的端口号为67发送请求消息到...

Dns Zone

DNS Zone是指域名系统(DNS)中管理的一部分域空间,包含了该区域内所有域名的记录信息。它是确保网络中计算机能够通过域名解析到正确 IP 地址的关键组成部分,对于维持互联网的正常运行至关重要。一个DNS Zone通常由一个组织或个人负责维护,这个区域包括了域名及其子域的权威信息,如IP地址、邮...

DNS

DNS配置与匹配规则

1. multi dns ip 对于一个主机配置多个DNS IP 1.1. 作用 1.1.1. 冗余和容错 - 当第一个DNS服务器无法响应时,主机会自动尝试使用第二个、第三个DNS IP - 提高DNS解析的可靠性,即使一个DNS服务器宕机,仍可通过其他DNS服务器进行域名解析 1.1.2. 负载...

Https抓包

由于主流浏览器都只支持HTTP/2 Over TLS,也就是说当前HTTP/2网站都使用了HTTPS,数据传输都经过了SSL加密,常规抓包方法并不能看到明文数据。 1. wireshark Wireshark 的抓包原理是直接读取并分析网卡数据,要想让它解密 HTTPS 流量,有两个办法: 1)如果...

HTTP协议解析

1. HTTP 1.0 !http1.0抓包.pcapng HTTP/1.0 默认为每一对 HTTP 请求/响应都打开一个单独的 TCP 连接。 - 17945-17947:tcp三次握手 - 17948:Server告诉Client更新自己的接收窗口大小 - 17949:Client发起HTTP ...

TLS

1. TLS是什么 Transport Layer Security (TLS) 是一种被广泛采用的安全协议,旨在增强互联网通信的私密性和数据安全性。TLS的主要使用场景是对Web应用和服务器之间的通信(例如,Web 浏览器加载网站)进行加密。TLS 还可以用于加密其他通信,如电子邮件、消息传递和 ...

常用Http Header

1. remoteaddr 表示发出请求的远程主机的IP地址,remoteaddr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时 1. 假设中间没有任何代理,那么网站的web(,Apache等)就会把remoteaddr设为你的机器IP 2...

应用层

ARP

1. arp响应的条件 1.1. 普通主机响应 ARP 请求的条件 当本机是普通主机时,只有当 ARP 请求中的目标IP地址与本机IP地址匹配时,普通主机才会发送ARP响应。例如,主机A的IP地址为192.168.1.10,当它接收到ARP请求,且请求的目标 IP地址为192.168.1.10时,主...

VLAN

1. 介绍 当网络上所有设备在同一个广播域产生大量的广播和多播帧(arp/dhcp/stp/rip),就会与业务数据流争带宽,造成网络性能恶化。将大型广播域分段是提高网络性能的方法之一。路由器能够将广播包阻隔在一个三层接口上,但是路由器的LAN接口数量有限,它的主要功能是在三层网络间传输数据,而不是...

交换机原理

1. SAT(MAC地址表、FDB、CAM) 交换机的Source Address Table(源地址表),也称为MAC地址表、FDB(forwarding database)或CAM(Content Addressable Memory),是一种用于存储和管理设备MAC地址和端口之间映射关系的表格...

概念解释

ISP: Internet Service Provider 互联网服务提供商 IXP: Internet eXchange Point 互联网交换点 通信方式: - 客户-服务器方式(C/S) - 对等方式(P2P) 1. 电路交换 电路交换必须经过“建立连接(占用 通信资源)一通话(一直占用通信...

IPSec

IPSec (Internet Protocol Security) 协议主要工作在OSI模型的第3层网络层,为上层传输层协议(如TCP、UDP等)提供安全支持。IPSec不是一个单独网络协议,而是一系列为IP网络提供安全性的协议和服务的集合,提供了认证、加密、数据完整性等安全服务。 1. 主要协议...

仿真

Ipv4地址

1. 特殊ip地址汇总 - 0.0.0.0/8:用于广播信息到当前主机 - 10.0.0.0/8:用于专用网络中的本地通信 - 172.16.0.0/12:用于专用网络中的本地通信 - 192.168.0.0/16:用于专用网络中的本地通信 - 100.64.0.0/10:用于在电信级NAT环境中服...

IPv6地址

1. 地址格式 IPv6地址有128bit,在这128bit中,前64bit是网络前缀,后64bit是接口标识。在前64bit中,前48bit又是全球可汇总地址,在给一个公司分配IPv6地址时,总是分配给它一个前48bit固定的地址,而后面的16bit又可以被该公司用来做子网地址。这样分配,可以方便...

网络层

链路本地地址

链路本地地址(Link-local Address)是一类特殊的IP地址,仅用于在网段内、同一广播域内的主机相互通信使用,这类主机可认为是不需要外部互联网服务的。其中IPv4的链路本地地址定义在169.254.0.0/16范围内,IPv6定义在fe80::/10范围。链路本地地址在ipv4和ipv6...

BGP

BGP基础概念

1. AS - OSPF、IS-IS等IGP路由协议在组织机构网络内部广泛应用,随着网络规模扩大,网络中路由数量不断增长,IGP已无法管理大规模网络,AS的概念由此诞生。 - AS指的是在同一个组织管理下,使用统一选路策略的设备集合。 > [!question] 不同的AS之间需要进行通信,在AS之...

BGP邻居建立

- 先启动BGP的一端先发起TCP连接,R1先启动BGP,R1使用随机端口号向R2的179端口发起TCP连接,完成TCP连接的建立。 - 三次握手建立完成之后,R1、R2之间相互发送Open报文,携带参数用于对等体建立 - 参数协商正常之后双方相互发送Keepalive报文,收到对端发送的Keepa...

路由协议

CPU

1. 基本概念 - Socket: 指的是主板上用于安装物理CPU芯片的接口。每一个这样的接口能够插入一个物理的CPU处理器,一台计算机可以配备多个这样的插槽,进而安装多个物理CPU,以增强系统的计算能力和性能。一个物理CPU可以有多个物理CPU核。 - 物理核(): 可以看的到的,真实的cpu核,...

File System

常见文件目录

各大发行版基本一致,遵循的是通用的FHS(Filesystem Hierarchy Standard)约定,(个别子目录如 lib64、multiarch 名称可能略有不同) 1. 开发者部署模板 用一个假想应用myapp为例,展示其在Linux系统中应该如何分布文件和目录。 🏗️ myapp 部...

Linux

Bridge

1. 常见命令 2. 泛洪 - 泛洪机制:桥接设备会将该数据包从除接收端口之外的所有其他端口发送出去。这样做的目的是确保目的设备无论连接到哪个端口,都有机会接收到这个数据包。例如,在一个包含多个主机的局域网中,如果主机 A 向一个桥接设备发送了一个数据包,而该桥接设备的 FDB 表中没有目的主机的 ...

Geneve隧道配置

1. geneve协议格式 GENEVE与VXLAN类似,仍然是Ethernet over UDP,也就是用UDP封装Ethernet。VXLAN header是固定长度的(8个字节,其中包含24bit VNI),与VXLAN不同的是,GENEVE header中增加了TLV(Type-Length...

Network Namespace基础

可以创建一个完全隔离的新网络环境,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables等等。总之,与网络有关的组件都是独立的。 可以看到我们刚才创建的网络环境 进入虚拟网络环境,使用命令 只能看到lo口 这样我们可以在新的网络环境中打开一个shell。 连...

Route Table与VRF

在 Linux 系统中,支持使用多个 route table(路由表) 是为了增强网络配置的灵活性和功能性。多路由表的主要作用如下: 1. 实现策略路由(Policy Routing) 默认情况下,Linux 使用主路由表()中的路由规则来决定流量的转发路径。然而,某些场景下可能需要基于流量的源地址...

Tcp关键内核参数

1. tcpsynretries 这个参数值设置的是client发送SYN如果server端不回复的话,ff0000">重传SYN的次数。对我们的直接影响就是connet建立连接时的超时时间。当然Java通过一些C原生系统调用的组合使得我们可以进行超时时间的设置。在Linux里面默认设置是5,下面给...

Tun Tap介绍

在计算机网络中,TUN(Tunnel)与TAP(Test Access Point)是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。 - TAP等同于一个以太网设备,它操作第二层数据包...

Vxlan隧道配置

1. 主机应用通过绑定独立路由表或vrf的方式通过隧道访问对端 > 图为网图,本文配置vtep地址换成了10.0.0.250和10.0.0.251 250主机配置 查看当前系统信息 > 虽然从概念上讲,VRF 主要用于实现逻辑上的路由隔离,但在系统的网络配置和管理中,它被呈现为一个类似普通网络接口的...

管道

1. client 2. server 3. ouput 数据直接在内存中的管道缓冲区传输

网卡混杂

网卡在混杂模式下不会校验收到的报文的mac和ip

CPU使用率

1. 查看进程CPU使用率 1.1. 平均使用率 ps命令显示的值是进程的平均CPU使用率,计算公式为: $\%CPU=(进程使用的CPU时间​/进程运行的总时间)×100$ 如下命令显示CPU占用最高的前几名进程 输出如下 1.2. 瞬时使用率 如果想监控某个进程(比如 )的 CPU 使用率 ...

Ls

1. 基于文件名排序 2. 基于文件大小排序 3. 基于文件时间排序

Lsof

(List Open Files)是一个用于列出系统中打开文件的工具。这里的 “文件” 是一个广义的概念,包括常规文件、目录、块设备、字符设备、共享库、网络套接字等。会列出系统中当前用户打开的所有文件。 : 列出指定进程 ID 的进程打开的文件 :列出指定用户打开的文件 : 列出正在执行指定命令的进...

PS

命令是process status的简称,用于显示当前运行的进程的信息。在不使用任何标识的情况下,会显示所有当前用户启动的进程,比如: - PID: 进程的ID号 - TTY: 终端名称缩写 - TIME: CPU时间,即进程使用CPU的总时间 - CMD: 所执行的命令名称。 1. 参数 命令支持...

Sort

-f :忽略大小写的差异,例如 A 与 a 视为编码相同; -b :忽略最前面的空格符部分; -M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法; -n :使用『纯数字』进行排序(默认是以文字型态来排序的); -r :反向排序; -u :就是 uniq ,去重,相同的数据中,仅出现一...

常用运维命令

核隔离和Cgroup

!CPU1. 基本概念 1. 核隔离 修改内核启动参数 1.1. CPU加压测试 htop可以看到隔离的核没有受到影响 2. 绑定进程到隔离核心 3. cgroup 输出如下 > [!info] > Worker 0 started on CPU 2 Worker 2 started on CPU ...

Numa架构

是一种多处理器架构,它将系统中的内存分成多个节点,并将每个节点分配给不同的处理器。在 NUMA 架构中,每个处理器可以访问本地节点的内存,但访问远程节点的内存速度较慢。因此,NUMA 架构可以提高多处理器系统的性能和可扩展性。 下图为英特尔S2600系列服务器主板 - 两个CPU插槽,CPU插槽之间...

Os

Page Cache

文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被CPU访问。由于读写硬盘的速度比读写内存要慢很多,所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux内核使用页缓存(Page Cache)机制来对文件中的数据进...

中断

1. 中断(Interrupt)的定义 中断是计算机系统中一种关键机制,允许处理器暂停当前执行的任务,转去处理更高优先级的紧急事件,处理完成后恢复原任务。中断机制提高了系统的响应速度和资源利用率,是操作系统实现多任务、实时响应的基础。 2. 硬件中断(Hardware Interrupt) vs 软...

操作系统地址空间

操作系统的地址空间是一个抽象概念,表示操作系统在运行时能够管理和访问的内存范围。它提供了一种将物理内存映射到逻辑地址的机制,使得程序能够在一个独立于物理硬件的虚拟地址空间中执行。 1. 用户空间与内核空间 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所...

操作系统概述

1. 操作系统基本特征 1.1. 并发 并发是指两个或多个事件在同一时间间隔内发生,在多道程序环境下,一段时间内宏观上有多个程序在同时执行,而在同一时刻,单处理器环境下实际上只有一个程序在执行,故微观上这些程序还是在分时的交替进行。操作系统的并发是通过分时得以实现的。操作系统的并发性是指计算机系统中...

进程调度策略

进程调度的关注点: - 性能:执行完所有任务需要的总时间。 - 公平性:每个任务都期望自己先被执行,尽早完成。 - 响应时间:首次运行时间 - 任务提交时间。 性能、公平性、响应时间是矛盾的,为了达到最好的性能,我们希望cpu不要在不同进程或线程之间切换,这样避免了上下文的切换,一个任务从开始执行到...

Openssl

1. RSA密钥 生成私钥 - :指定算法为 RSA - :指定密钥长度为 2048 位(可改为 4096 - :输出私钥文件路径 生成公钥 - :输入私钥 - :导出公钥 - :输出公钥文件路径 2. ECC密钥 生成对应的ECC私钥 - :指定椭圆曲线(可换成 , , , 等) - :生成密钥 ...

SSL证书

在密码学和数字证书体系中,一般没有“私钥证书”的说法。主要是公钥证书。公钥证书(通常就简称为数字证书)是一种电子文档,包含以下信息 - 公钥信息 - 持有者身份信息(如姓名、组织等) - 证书颁发机构(CA)的数字签名等 其主要目的是将公钥与特定的实体(如个人、服务器等)绑定,用于在网络通信等场景中...

Webauthn

在数字时代,密码已成为我们日常生活和在线活动中不可或缺的一部分。尽管互联网已经发展了20多年,许多方面都有了巨大的改进,但只有密码,还是20年前的用法。更准确的说,它的用户体验比 20 年前更差了。 1. 密码的强度现在要求越来越高,一般不能少于8个字符,还要包括特殊符号。 2. 除了密码,通常还有...

Tech

CPU架构

1. 主频 CPU 主频,即CPU的时钟频率,是衡量 CPU 运算速度的重要指标,以下从其定义、工作原理及与性能的关系等方面进行具体解释。 - 基本定义:CPU 主频表示 CPU 内数字脉冲信号振荡的速度,单位为赫兹(Hz)。通常所说的CPU 主频为2.4GHz、3.6GHz等,意味着CPU内部的时...

CPU缓存设计

1. 缓存 Cache Memory也被称为Cache,是存储器子系统的组成部分,存放着程序经常使用的指令和数据,这就是Cache的传统定义。从广义的角度上看,Cache是快设备为了缓解访问慢设备延时的预留的Buffer,从而可以在掩盖访问延时的同时,尽可能地提高数据传输率。 快和慢是一个相对概念,...

CPU调度策略

1. MLFQ(Multi-Level Feedback Queue) - 系统中存在多个就绪队列,每个队列有不同的优先级,高优先级队列中的进程会优先获得 CPU 执行。 - 进程在队列之间可以移动,根据其行为(如 CPU 占用时间、I/O 操作等)动态调整优先级。 1.1. 规则 1.1.1. 优...

Blog Online

TODO

- BGP实践,BGP PEER建立 - 核隔离与CGROUP - NUMA - 中断 - 硬件卸载 - 大页 - etcd 1. cloud wan ccn一个vpc支持创建几个attachment ccn路由同步,会同步到未关联的attachment吗?默认会同步所有路由表吗?

Icloud

使用土区账号购买50G icloud,平均每个月2.5块 1. 土区账号登录icloud 2. 国区账号登录apple store 3. 闲鱼购买土区礼品卡充值到土区账号 4. 土区账号购买icloud 5. 过去账号通过airdop发送邀请土区账号加入家庭组 6. 土区账号共享icloud设置家庭...

Excel

1. 将多个sheet join合并 Power Query自动化合并(适合多列/大数据) 1. 导入数据到Power Query: - 在Excel中点击数据 > Power Query > 获取数据,加载Sheet1和Sheet2。 2. 合并查询: - 选择Sheet1作为主表,与Sheet2...

Vnc远程桌面

总览

Clash For Linux

Jupyter安装

z从sqlite官网,下载安装最新版本sqlite3 清理旧版本的sqlite3 安装并编译python环境 配置jupyter lab 添加python环境 1. 格式转换

Movie Pilot

1. docker 安装 注意要通过环境变量配置代理,否则无法访问themoviedb或thetvdb等 2. 下载与整理 可以通过软连接目录的方式达到下载目录和mp的/media目录一致。 3. ref https://wiki.movie-pilot.org/zh/install

Movie Bot

NAS搭建

1. 主机硬件 - 主板:微星B360M Mortar - CPU:i3-8300t - 内存:金士顿骇客8G 2 DDR4 - SSD:三星980 NVMe PCIe3.0 500G - 西数:红盘4T - 散热器:利民AXP90 X47 - 电源:台达VX350 - 机箱:先马米立方matx 2...

Openwrt

Ubuntu系统备份

1. 备份 2. 还原 如果当前启动无法启动,可以通过live cd来启动并执行恢复操作

Xiaoya

windows下必须使用bridge模式 1. docker安装 2. 定时清理 3. 获取元数据 在wsl的ubuntu子系统中执行 4. 参考

Advanced Table

obsidian 功能 - 自动化格式表格 - Excel样式的表格导航,即使用Tab和Enter在行和列之间导航 - 对指定的列进行函数求值 - 添加、删除、移动行和列 - 设置列的对齐方式 - 对指定列进行排序 - 将表格导出为CSV格式 公式 基本格式如下: 如中的 代表最后一行、第二列,右边...

Appearance

打开笔记仓库的 .obsidian 文件夹,其中如果没有 snippets 文件夹则创建。在 Obsidian 中的 CSS snippets 都是以 .css 的档案格式储存在特定的文件夹。如果你有将 CSS snippets 放到该文件夹,就会在 Obsidian 显示开关。 1. 别人给你的,...

Dataview

1 查询依据 yaml数据/metainfo 2 使用查询语言 使用下列语法创建查询语言代码块 dataview query command 3 使用内联查询 内联查询的结果只能是一个,不能查询一个列表。当前页面可以通过获得,其他页面可以通过双链语法获得 通过下列语法创建内联查询: 此博客文件名: ...

Excalidraw

obsidian 是一个手工风格的白板工具。可以使用呼出命令菜单,输入excalidraw进行创作。 library 提供了很多公开的模板库可以帮助我们画出很多精美的图案。 导出 可以导出为png或svg 双链 鼠标右键选中create link,可以在excalidraw中使用双链。和在markd...

Image Auto Upload Plugin

obsidian 使用aliyun oss作为obsidian图床 1. 下载 2. 配置oss作为图床 3. 在obsidian中粘贴图片后自动上传aliyun os

Mind Map

obsidian 1. 使用方式 使用呼出命令行,输入通过提示补全命令 2. pin 可以将思维导图的预览面板嵌到当前笔记中。 3. copy screenshot 将svg格式mind map复制到剪切板 4. bug修改 mind map已经很久没有维护了,对于代码块支持存在bug,会导致无法生...

Minimal Theme Setting

obsidian 设置主题 settings -> options -> appearance里面选择主题 Style settings 自定义包括字体等各种样式 Minimal Theme setting 里面内置了一些经典的配色,可以对主题进行一些快速设置

Obsidian Tasks

任务管理 常用语法 1. 今日之前(包括)已完成,done before 2. 本周截止当前日(包括)已完成,done after 3. 根据重要性排序,sort by priority reverse 示例 获取本周所有已经完成的任务,按优先级倒序排列 tasks done before done...

Obsidian总览

obsidian 1. 视图 obsidian一共提供了三种视图: - preview mode:预览模式,可以编辑,显示实时预览结果,此模式下metadata不会渲染 - reading mode:阅读模式,markdown渲染后结果,不可编辑 - source mode:以纯文本形式显示mark...

代理

ssh代理

效率工具

1. 添加环境变量 2. windows命令行代理 3. git代理 也可以直接编辑/.gitconfig文件 4. windows配置beyondcompare作为gitdiff 修改.gitconfig配置文件 5. 删除重复文件

Vault

Tcp异常断链

1. 现象 客服访问建行业务偶现超时。 建行server端主动发起断链,发送fin报文,客户B收到后回ACK报文,进入fin-wait-2状态 客户B服务端前设置有防火墙,fin状态下设置10秒超时,客户B超时后的rst报文被防火墙丢了,建行server无法收到rst报文,导致一直处于fin-wai...

业务偶现超时

1. 现象 客户A通过NAT网关访问客户B业务偶现超时。 2. 抓包分析 中间设备抓包发现,服务端主动断链,发送FIN报文并收到ACK报文完成两次挥手,但客户端并没有随之进行被动关闭,直到约20秒后发出rst报文,在此期间连接一直处于半关闭状态。而NAT网关fin超时时间为5秒,此时会话已经老化,再...

交换机选型要点

交换机选型

clos架构

CLOS网络的核心思想是:用多个小规模、低成本的单元构建复杂,大规模的网络。简单的CLOS网络是一个三级互连架构,包含了输入级,中间级,输出级。下图中的矩形都是规模小得多的转发单元,相应的成本也很低。简单来说,CLOS就是一种多级交换架构,在输入输出增长的情况下,增加中间交换单元的数量,而不是每个交...

NAT分片报文

前文我们聊到,在NAT地址转换中,NAT除了对IP地址转换外,还使用到TCP或UDP报文的端口号、ICMP报文的ICMP头中的identifier字段信息。当一个IP分片报文和TCP分段报文 1|IP报文被分成若干片之后,这些信息只有首片报文会携带,后续分片报文依靠报文ID、分片标志位、分片偏移量依...

NAT之ICMP

报文没有类似于或的端口信息,而正常NAT为了内网的安全性和IP地址的高利用率,一般都使用了五元组来匹配NAT会话。为了对ICMP的请求报文进行NAT映射,又要确保网络的安全性,只允许合法的ICMP响应报文进入内网,就必须对其进行特殊的处理。 1. ICMP请求和应答报文 ICMP的request和r...

NAT会话

我们知道传输层的任意一条流都是通过两个建立的,由组成,因此一条流可以用五元组表示。这个五元组中的任意一个元素都不能改变,否则就是另外一条流了。对于服务端来说,和中任意一个变了,就意味着一个新的接入连接;而对于客户端来说,和中任意一个发生变化,访问的就是一个新的服务,比如通常是一个服务,是一个数据库的...

Thrift协议

是一种轻量化、语言无关的RPC框架。主要包含三大部分:代码生成、序列化框架、RPC框架。 Thrift网络协议栈 采用的是模型,网络协议栈从下到上分别为:、、、。 Transport 传输层为网络提供了一个简单的抽象,定义了具体的网络传输协议。其本质是一个提供了通过各种协议进行通信的框架,支持包括和...

shell编程

本文主要介绍shell编程的基本语法以及实际应用中的常见命令。 1. 注释 1.1. 单行注释 1.2. 多行注释 多行注释也可替换成或 2. 变量 2.1. 变量定义 使用的形式,VALUE如果是字符串的话,可以使用单引号、双引号或者不加引号。单引号内的任何字符都会原样输出,不能进行转义,单引号内...

netstat命令

是基于Netstat这个命令行工具的指令,它可以用来查询系统上的网络套接字连接情况,包括,以及。另外它还能列出路由表,接口状态和多播成员等信息。 1. 参数选项 | 参数 | 作用 | | | --- | -----------------------------------------------...

系统信息查看命令汇总

在系统下经常要查看各种信息,需要使用到各种命令,全部背下来也是一项不小的工作量。每次临时找也比较浪费时间,这里汇总下常用的命令。 1. 系统 1.1. 查看linux内核版本 文件系统不是普通的文件系统,而是系统内核的映像。也就是说,该目录中的文件是存放在系统内存之中的,它以文件系统的方式为访问系统...

数据库隔离级别

本文主要目的是阐明数据库的四种隔离级别以及在mysql下实现隔离的原理。 1. 隔离级别 数据库事务隔离级别从低到高分别是:read uncommitted、read committed、retpeatable read、serializable,依次解决了数据库脏读、不可重复读、幻读问题。 - 脏...

用户和权限

使用 Linux 系统,不免会和用户和权限打交道,本文介绍了根权限和文件的权限属性等概念和应用。 1. 用户与群组 使用查看所有用户 使用查看所有用户组 修改文档所有者或群组 2. Linux权限 命令 - 代表三种身份owner/group/other,a代表全部身份all - 代表三种操作行为(...

进程管理

在 系统中,进程是资源调度的最小单位,进程的管理关乎着你使用系统的体验。 1. 进程类型 Linux 系统里有几种不同类型的进程:用户进程(User processes)、守护进程(Deamon processes)和内核进程(Kernel processes)。 1.1. 用户进程 系统里大多数进...

NAT概览

1. 背景 地址使用4个字节进行存储,最多能够提供个地址。随着互联网尤其是物联网的发展,全球地址早已不够用,因此人们发明了(网络地址转换)来缓解这个问题。 简单来说,国际互联网组织划分了三个网络地址段作为内部网络本地通信使用,分别是,,。大部分内部机器都使用这些网段中的私有地址,如果它们需要访问公网...

NAT Overview

1. What is NAT? 1.1. NAT(Level 4) NAT(Network address translation)即网络地址转换,工作在OSI模型的三层或四层(PNAT),用于修改IP数据包中的IP地址和端口。当在专用网内部的一些主机本来已经分配到了local ip地址,但又想和I...

iptables

iptables 是 Linux 防火墙工作在用户空间的管理工具,是 netfilter/iptablesIP 数据包过滤系统是一部分,用来设置、维护和检查 Linux 内核的 IP 数据包过滤规则。 1. 四表五链 数据包在经过每个的时候会按照每个链对应的表依次进行查询匹配执行的操作,如PRERO...

tcpdump

tcpdump是一个优秀的网络分析工具,提供了强大且简单的接口。 1. options - -i any 监听所有的网卡接口,用来查看是否有网络流量 - -i eth0 只监听eth0网卡接口 - -D 显示可用的接口列表 - -n 不要解析主机名 - -nn 不要解析主机名或者端口名 - -q 显...

数据中心网络架构

0.1. 数据中心 根据维基百科释义,指用于安置计算机系统及相关部件的设施,例如电信和存储系统。数据中心是全球协作的特定设备网络,用来在internet网络基础设施上传递、加速、展示、计算和存储数据信息。 0.2. 传统数据中心网络架构 如图1所示,传统的大型数据中心网络通常采用三层架构。cisco...

应用层DNS协议

(Domain Name System)域名解析服务采用架构,是一个应用层协议。的作用是将人类可读的域名(如:www.shinerio.cc) 转换为机器可读的 IP 地址(如:1111.111.111.111)。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。协议建立...

计算机网络(九)—— 传输层

传输层架构在网络层之上,在两台计算机进程之间传输数据,常见的传输层协议包括TCP和UDP。 1. TCP 1.1. 首部格式 1.2. TCP状态机 TCP是面向连接的,在其生命周期会有各种不同状态 | 状态 | 描述 | | ------------ | ---------------------...

路由协议

路由是选择路径并将报文沿着选择的路径进行转发的过程。 1. 路由器 1.1. 路由器功能 路由器从功能上可以划分为: - 路由选择:使用路由协议(或手工静态配置的方式)获取整个网络的拓扑结构,构造、维护路由表。 - 分组转发:分组转发结构由交换结构、一组输入端口和一组输出端口构成。 1. IP分组检...

计算机网络(七)—— 网络层 —— ICMP

互联网控制消息协议(Internet Control Message Protocol,ICMP)是TCP/IP协议族的核心协议之一,用于IP协议中发送控制消息,提供可能发生在通信环境中的各种问题反馈。通过这些消息,使管理者可以对所发生的的问题作出诊断,然后采取适当的措施解决。ICMP可以简单认为是...

数据链路层概述

数据链路层使用的信道主要分为以下两种: - 点对点信道,使用一对一的点对点的通信方式 - 广播信道,使用一对多的广播通信方式,广播信道上连接的主机很多,因此必须使用专用的共享信道协议来协调这些主机的数据发送。 0.1. 数据链路 当需要在一条线路上传送数据时,除了必须有一条物理线路外,还必须有一些必...

计算机网络(三)—— 网络的基本分类

本文主要介绍计算机网络的分类以及局域网技术。 0.1. 网络分类 - 地理位置: 1. 个域网(PAN,Personal Area Network)。个域网允许设备围绕一个人进行通信。一个常见的例子是计算机通过无线网络(蓝牙)与其外围设备(显示器、键盘、鼠标。打印机)连接。 2. 局域网(LAN,L...

计算机网络(二)—— 性能指标

计算机网络的常见的性能指标有速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率。 1. 速率(bit/s或byte/s) 速率是物理层概念,指的是信道上每秒钟传输的0/1比特数量。比如下载文件中显示的7.8MB/s指的是每秒中可以传输7.8M字节的信息。 2. 带宽(bit/s) 带宽是逻辑概念...

计算机网络(一)—— 分层模型

计算机网络按照使用范围可以划分为个域网、局域网、城域网、广域网和互联网,其组成复杂,存在着大量诸如集线器、交换机、路由器、笔记本、手机等硬件。为了降低网络设计的复杂性,现阶段大部分网络都会形成一个层次栈结构,每一层都建立在其下一层的基础之上,向上一层提供特定服务。这种分层结构的概念其实和计算机领域的...

加密算法

本文主要介绍密码学中常见的两种加密算法—对称加密和非对称加密。 1. 散列(摘要)算法 在学习加密算法之前,我们先来了解一下散列算法(散列不是加密)。散列算法是通过一定方式对原文进行计算,产生一个哈希值,不管原始数据是什么样的,得到的哈希值都是固定长度的,其作用只是为了验证数据的完整性和唯一性,无法...

© 2026 shinerio. All rights reserved.

} | Select-Object FullName\n\n# 实际删除(确认无误后使用)\nGet-ChildItem -Path \"D:\\document\" -File -Recurse | Where-Object {$_.BaseName -match '^.+ \\d{1,2}

Github Oauth

▐▛███▜▌ Claude Code v2.1.34 ▝▜█████▛▘ Opus 4.6 · Claude API ▘▘ ▝▝ C:\workspace\code\shinerio.github.io > 1. 文章详情页,点击划词后,点击使用github登录,页面直接404,需要修复 2. 底...

BGP Speaker

在传统的网络设备中,我们通常直觉地认为“一台设备 = 一个 BGP 进程 = 一个 Speaker”。但在现代网络设计和高级设备架构中:一台物理路由器可以拥有多个 BGP Speaker。 为了理解这一点,我们需要区分“物理实体”和“逻辑协议实例”。以下是几种实现“多 Speaker”的典型场景: ...

BGP路由器分类

在BGP(边界网关协议)中,路由器的分类通常根据其在自治系统(AS)中的位置以及建立邻居关系的方式来划分。 1. 按协议划分 1.1. EBGP 路由器 (External BGP) 当两台运行 BGP 的路由器处于不同的自治系统(AS)时,它们之间建立的邻居关系称为 EBGP。 - 位置: 通常位...

Awesome Tools

1. 全平台 - freefilesync,文件夹同步工具 - ChatGPT流媒体解锁检测脚本: - https://gitlab.com/fscarmen/warp解锁应用端chatgpt 2. macos 2.1. 媒体工具 - snipaste截图软件 - kap屏幕gif录制工具,对于博客...

Claude Code

1. claude code配置 1.1. ide集成 在vscode中安装claude code插件,然后在claude code命令行界面使用即可连接到vscode。claude code就可以和vscode进行交付,感知你在vscode中选中的代码、文件,claude code的修改也会在vs...

Subagents

1. 子代理是专门处理特定类型任务的 AI 助手,当Claude遇到与子代理描述相匹配的任务时,它会将任务委派给该子代理,由其独立工作并返回结果。 - 每个子代理都在自己的上下文窗口中运行 - 拥有自定义的系统提示词(System Prompt) - 特定的工具访问权限和独立的权限设置。 优势: -...

Cloudflare免费Worker

cloudflare一段时间之前推出的一项免费服务, 允许在CDN服务器上运行js脚本或wasm 截止到这篇文章写完的时候,这仍是一项长期免费服务,免费套餐为每天 100000 个请求, 大概是100个人 每人请求100次, 或0.01个人 每人请求10000000次

Quantumult X

1. 参考配置 https://raw.githubusercontent.com/limbopro/Profiles4limbo/main/full.conf 2. 规则仓库 https://github.com/blackmatrix7/iosrulescript/tree/master 3. ...

Cloud Wan竞品分析

1. 腾讯 - CCN路由同步,默认会同步所有路由表 - - VPC多条路由冲突的时候,可以支持启用、停用 1.1. 跨地域流量管理 - 单向的流量调度规则限速带宽总和不得超过带宽上限。 - 带宽上限提高后,默认规则的限速带宽不会自动调整,需手动调整 2. 阿里 - CEN路由同步,默认只会同步默认...

IBGP与EBGP区别

1. 使用场景 eBGP主要用于: - 不同运营商/组织之间交换路由,例如中国电信与中国联通的互联互通 - 企业多归属(multihoming)接入多个 ISP - IXP(互联网交换点)中各参与方之间的路由交换 iBGP 主要用于: - 在一个大型 AS 内部传递从 eBGP 学到的外部路由信息,...

BGP协议优先级

1. 核心逻辑 1.1. “大”即是好的(高优先级) Weight 和 Local Preference:这两个属性是管理员手动干预的首选。数值设置得越大,代表你越“偏好”这条路径。 1.2. “短/小”即是好的(低开销) - AS-Path 长度:这体现了 BGP 的路径矢量特性,跳数越少代表路径...

Openspec

A "change" in 是一个“承载着围绕一项工作所进行的所有思考和规划的“集合。文件夹位于,包含proposal, specs, design, tasks。 工作流程 目录结构 - - 这是最重要的目录,存储了系统当前是如何运行的完整描述。 - 按domain组织:为了防止单个文档过大,它按...

Claudemd

| 类型 | 位置 | 范围 | 版本控制 | 典型用途 | | ------ | ----------------------- | ---- | ----- | ------ | | 全局 | | 所有项目 | 不共享 | 个人编码偏好 | | 项目 | | 当前项目 | 共享给团队 | 团队规...

SDD(Spec Driven Develop)

Kiro的spec流程被设计为三个步骤:需求 (requirements.md) → 设计 (design.md) → 任务 (tasks.md)。每个工作流步骤都由一个Markdown文档表示,Kiro会引导你达成这三个步骤。 1. 需求文档 它被构建为一个需求列表 1. 每个需求代表一个“用户故...

Run Code

runcode是一个来自veadk库的内置工具,它提供了一个安全的代码执行沙箱功能。这个工具允许AI Agent运行用户请求的代码片段(主要是 Python3),并返回执行结果。该工具通过字节跳动云服务的API在远程安全环境中执行代码,并具有会话管理和身份验证功能。 1. 工作原理 1.1. 工具注...

Demo

1. summary 2. 3. 对话前,无任何相关记忆 通过对话告知喜欢滑雪 再次查询记忆库 4. 5. 部署MCP服务的时候可以选择API KEY自动生成MCP的入站身份鉴权 agentkit一直无法拉起mcp工具集,这里使用claude code本地测试 6. 环境变量参考 7. 相关链接 -...

Awscli

install 配置账号 1. ref https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

Invoke

调用需要具备以下权限

Introduce

Amazon Bedrock AgentCore 同时具备框架无关性和模型无关性,让你能够灵活、安全且大规模地部署和运行高级 AI 智能体。无论你是使用 Strands Agents、CrewAI、LangGraph、LlamaIndex 或任何其他框架构建智能体,也无论你在哪个大语言模型(LLM)...

Gateway

1. v2n agent runtime需要的short-term memory需要通过v2n连接到用户vpc中的postgresql(内存中易失)

AgentCore

Amazon Bedrock AgentCore是用于构建、部署、高效运行Agent的最先进的智能化平台,其服务包括Runtime, Gateway, Memory, Identity, Observability, Browser, Code Interpreter, Evaluation, po...

Oauth2.0

OAuth 2.0是一个关于授权(Authorization)的开放标准。它允许用户让第三方应用访问该用户在特定服务上存储的私有资源(如照片、联系人等),而无需将用户名和密码提供给第三方应用。最常见的例子就是:你使用“微信登录”或“GitHub登录”来注册一个新的网站,而不需要在该网站重新设置密码。...

记忆

1. 短期记忆 LLM的核心架构在推理时,每一轮都是独立的计算过程。因此如果不把所有对话历史都带上的话,LLM就只能针对当前问题就会回答,无法感知历史对话。 在AI中,短期记忆通常指“对话上下文”,包括: - prompt - 对话历史 - 前序大模型推理结果 - 前序工具执行结果 短期记忆有两种存...

开源模型汇总

1. | Model | Total Params | Activated Params | Context Length | | :--------------: | :---------------: | :-------------------: | :----------------: | ...

Uv

1. 设置pip源 配置环境变量 2. 设置cache位置 3. 初始化项目并指定最低python版本 4. 全局安装tool 如果安装Python包是为了在终端任何地方运行它的命令(比如 , , 或者你提到的 ),可以使用 : - 效果: 它会为这个工具创建一个隐藏的独立环境,但把它的可执行命令软...

MCP Gateway

1. ref https://www.volcengine.com/docs/86681/1844858?lang=zh

Agentkit

1. 支持协议: AgentKit智能体运行时支持A2A、MCP、标准HTTP三种通信协议 2. agent访问方式 - 公网访问:默认访问方式。 - 私网访问:选择同地域中的任意一个VPC和子网,每个可用区支持最多选择一个子网。 3. 大模型 通过api endpoint对接的是火山方案 3.1....

Huoshan

基本概念

1. 参数大小 | 缩写 | 英文全称 | 中文含义 | 数值(科学计数法) | 对应中文单位 | | ------ | ------------ | -------- | ------------- | ----------- | | M | Million | 百万 | $10^6$ | 100...

Bedrock

1. Model catalog - Amazon Bedrock Foundation Models - Amazon Bedrock Marketplace - Bedrock Custom Model Import 1.1. 区别对比 | 特性 | Foundation Models | Ma...

Awesome Tools

:pdf论文翻译,提供中英文对照。

Claude Desktop通信过程

MCP

MCP与AI Agent

AI Agent向LLM “提供” 了它能使用的工具列表,这个过程通常通过以下两种方式实现: 1. 静态工具注册(Static Tool Registration): - 在设计 AI Agent 系统时,开发者会预先定义并注册一系列可供 Agent 调用的工具。 - 每个工具都有一个清晰的 描述 ...

MCP开发原理

1. MCP Server开发 1.1. 注意事项 - 使用stdio作为transport layer的时候,不要进行任何控制台输出 1.2. 交互过程 INITIALIZE = "initialize" - 协商协议版本 - primitives支持情况,如tools、resources、pro...

MCP概念理解

1. 基本概念 MCP(Model Context Protocol 模型上下文协议) 是一个开放的标准化协议,用于在AI模型和外部数据源、工具之间建立安全、可控的连接。它定义了AI系统如何访问和利用外部上下文信息的规范。MCP就像是AI应用程序的USB-C接口,为AI模型提供了一种标准化的方式来连...

MCP配置

1. 基本定义 - 每个MCP服务器都是一个独立json对象,以服务器名称作为key - key在MCP配置文件中以及全局配置文件和项目配置文件中必须是唯一的 - 每个MCP服务器条目对象都必须具有属性 2. local mcp server - (可选):在执行之前,先将进程的工作目录切换到指定路...

开发与测试

1. tools 1.1. stdio 启动命令 inspector调试 mcp config 1.2. SSE 启动命令 inspector调试 mcp config 1.3. streamable http mcp-server仅需修改transport即可 mcp confi需要将修改为,例如...

Prompt

概念

1. Message类型 System message 在大模型内部是每次加在了用户输入的前面。在 的大模型设计的时候,有三种不同的message 类型,这三者是有明显区别的。 - System Message:对大模型的角色进行定义,并输入一些基础的指令,包括大模型的身份、一些用于提高安全性的指令...

RAG

RAG(Retrieval-Augmented-Generation, 检索增强生成)is a process that helps AI models "look things up" before they answer, like accessing my calender or the we...

Vector Database

Embeddings用数值形式的向量,在高维空间表示数据(通常是文本等非结构化的数据)。传统的关系型数据库并不适合存储和搜索这些向量表示。 向量存储库能够使用相似度算法对相似向量进行索引和快速搜索,使得应用程序能够在给定目标向量的情况下找到相关向量。 例如,在个性化聊天机器人的案例中,用户会向生成式...

AI

Agntcy

1. OASF Open Agentic Schema Framework一个基于OCI(Open Container Initiative)的可扩展数据模型,用于描述agent的属性并确保agent的唯一标识。OASF支持描述A2A代理和MCP服务器,并且可以扩展以支持其他常用格式,例如Copil...

Claude Skills

1. 原理 claude skill是一类模块化能力组件,用于拓展 Claude的功能边界。每项技能都封装了: 1. 元数据 2. 指令说明 3. 可选配套资源(脚本、模板) Claude Agent Skills的设计哲学在于模块化与按需加载,旨在解决传统代理系统中常见的上下文冗余、性能衰减以及操...

CrewAI

1. install https://docs.crewai.com/en/installation

执行计划

1. explain和explain anayze 和生成的执行计划通常是一致的,但并不能保证完全一致。 1.1. 核心功能对比 | 对比维度 | EXPLAIN | EXPLAIN ANALYZE | | -------- | ------------- | --------------- | |...

Excalidraw使用示例

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements %%>>>text element-link:Excalidraw<<<%%shinerio's blog ^0js7...

锥型NAT和对称型NAT

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== %% Drawing %%

Mysql可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

Mysql当前读下幻读问题

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Excalidraw Data Text Elements 事务A ^skXrctjq 事务B ^eJoYKov2 insert into ord...

Mysql当前读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^yULSUGNg 事务B ^RjMMNDeM select from orders where 10 sel...

Mysql快照读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^b7TiLH2p 事务B ^bMLs1cuE select from orders; ^nEabCTcv i...

Postgresql不可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

A2A

协议是一项开放标准,旨在解决人工智能快速发展领域中的一个基本挑战:由不同团队构建、使用不同技术且归不同组织所有的AI Agents,如何有效通信与协作? 随着人工智能代理变得更加专业化和强大,它们在复杂任务上协同工作的需求也日益增长。想象一下,用户让其主要的人工智能助手规划一次国际旅行。这一个请求可...

AI Agent

AI Agent综述(Chatbot + Workflow + AI Agent + Function Call + MCP)

1. LLM E.G. Chat GPT, Google Gemini and claude. 1.1. Work Mechanism Human provide an input and the LLM responds with an output. > input(prompt) -> LLM...

设计模型(Work Flow & Agent)

1. 工作流模式(Predefined Workflow) 1.1. 流水线 流水线是最简单直接的工作流编排,通过编排一个顺序处理的流程,让模型逐步执行和推理。 场景示例: 文档处理系统:文档上传 → 格式转换 → 内容提取 → 语义分析 → 结果存储 1.2. 路由分发 路由分发是将输入分类(LL...

AI Code

AI Platform

Aws

Dify

1. chatbot - 核心就是对话 - 支持自定义System prompt - System prompt和Conversation opener支持jianjia变量渲染 - 支持rag 输入变量后,进行对话 2. Agent Agent相比chatbot增加了工具调用的能力 2.1. Co...

字典树(Trie)

1. 单级字典树 字典树最基础的应用——查找一个字符串是否在「字典」中出现过,也可以用来做最长前缀匹配。 如下图,每个路径代表一个字母,每个节点存储以该节点结尾的字符串是否存在,构建如下的一个字典树。 1-4-8-12,且12节点记录值为true,则代表存在这样的路径的字符串,即存在caa字符串。 ...

时间轮

时间轮(Timing Wheel)是George Varghese和Tony Lauck在1996年的论文实现的,是一种实现延迟功能(定时器)的精妙的高级算法,其算法应用范围非常广泛它在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一,在Java开发过程中常用的Dubbo、Net...

Dapr(Distributed Application Runtime)

dapr重点落在了runtime上,runtime是一个抽象概念,提供了运行时的实现,不需要开发人员操心,比如Java的runtime环境就是jvm。核心思想是模块化,通过sidecar的方式实现,然后通过本地rpc或者http调用。 1. Multi Runtime 分布式应用的需求: - 生命周...

Cloud Network

EIP分类

1. 全动态BGP(多线EIP) 云服务提供商的公网IP地址通过BGP与多个运营线直连的链路播报给多个运营商。BGP类型的带宽具备动态路由收敛能力,可靠性和抗DDos能力好,但价格相对静态BGP来说较贵。云服务提供商可以根据设定的寻路协议实时自动优化网络结构,以保持客户使用的网络持续稳定、高效。 2...

ER的作用

使用peering等方式构建的网络结构是Full Mesh,而企业路由器的网络结构是中心辐射型(星型拓扑)。

Aliyun LB介绍

1. ALB - 每个可用区占用三个ip,一个vip,两个local ip - 每个vip可以独立绑定eip - 。ALB实例的杭州可用区H发生故障时,ALB能够在短时间内停用该可用区,并继续使用其他启用的可用区提供服务。 - ALB默认开启跨AZ负载均衡,即ALB在同地域跨可用区的后端服务之间分配...

AWS LB介绍

应用程序负载均衡器(ALB)、网络负载均衡器(NLB)和网关负载均衡器(GLB)是云中使用的三类负载均衡器。 为了重定向应用程序流量 - ALB 会检查请求的内容,例如 HTTP 标头或 SSL 会话 ID - NLB 会检查 IP 地址和其他网络信息,以最佳方式重定向流量 - GLB 充当透明的网...

HW LB介绍

1. LB实例 - 可用区:选择部署的可用区列表,可用区越多,价格越贵 - 应用型和网络型:可组合选择购买应用型和网络型,组合不同,收费不同 - 网络配置: - 所属VPC - 网络类型:ipv4、ipv6,可组合选择,或都不选(只提供公网接入);选择ipv4后,可提供私网IP接入的私网负载均衡;选...

Nginx使用

1. nginx常用命令 Nginx的命令在控制台中输入就可以看到完整的命令,这里列举几个常用的命令: - nginx -s reload 向主进程发送信号,重新加载配置文件,热重启 - nginx -s reopen 重启 Nginx - nginx -s stop 快速关闭 - nginx -s...

Nginx架构与原理

1nginx默认的启动方式是多进程的方式,nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。worker的数量可以通过配置文件中的配置项控制,一般建议设置与机器cpu核数相当。 Nginx采用了事件驱动的模型,期望worke...

Aws

aws的nat网关作为vpc的一个特性,创NAT网关的时候可以选择公网还是私网。流量经过NAT网关,源地址都会转成网关的私网地址,公网NAT后面会经过internate gateway,然后将私网地址转换成公网地址。 1. 公网NAT 1.1. 性能 - 默认5 Gbps,最高100 Gbps - ...

AZURE

1. 公网NAT 只支持SNAT,不支持DNAT 1.1. 性能 - 50 Gbps - 100W - 5M PPS 1.2. other 1.2.1. 支持自定义TCP超时时间 1.2.2. 支持一组地址连续的公网地址池 支持16个EIP或/28大小的公共EIP前缀 1.2.3. 分布式VM提供服...

GCP

google的NAT网关比较特别,不是一个独立的网关设备,而是做在源计算节点上的源端NAT。Cloud NAT 是一种没有代管式中间代理的软件定义解决方案,采用,具有高可靠性、高性能和高可伸缩性。 1. 公网NAT Public NAT 网关会为使用网关创建与互联网的出站连接的每个虚拟机分配一组外部...

华为云

1. 竞争优势 不需要使用peering/er等实现跨vpc访问的能力,私网nat天然支持跨vpc网络访问。

腾讯云

1. 公网NAT 1.1. 性能 - 默认限速5 Gbps,最大可支持50 Gbps - 并发连接200万 - 10万新建 - 不占用vpc用户的私网ip 1.2. 配额 1.2.1. 网关 - 一个VPC支持3个NAT网关 - 每个NAT网关绑定最多10个EIP 1.2.2. SNAT - 单网关...

阿里云

1. 公网NAT 2. 特性 - 支持一键全通 - snat只允许修改eip - - 支持按量计费和带宽包 2.1.1. NIS(Network Intelligence Service) 支持实例级别的一键诊断,可以检测实例的配置与运行状态,并能根据诊断的异常项提供智能修复建议。诊断内容主要包括:...

Aws HyperPlane

- top完成所有包转发和包处理,一旦网络连接建立了,转发只在Top层完成 - Flow Master记录网络连接,充当Decider的缓存 - Decider实现网络逻辑,对于NAT来说就是进行会话分配 Flow Master可以主主扩展,利用率可以做得比较高,而Decider利用率相对来说较低。

Dpdk

Ovs

现在OpenVSwitch主要由三个部分组成: - ovsdb-server:OpenFlow本身被设计成网络数据包的一种处理流程,它没有考虑软件交换机的配置,例如配置QoS,关联SDN控制器等。ovsdb-server是OpenVSwitch对于OpenFlow实现的补充,它作为OpenVSwit...

发展历史

1. 转发平台发展 - 以openvswitch为代表的第一代内核态软件转发平台 - 以dpdk为代表的第二代内核态软件转发平台 - 以P4或NP为代表的第三代硬件转发平台。 第一代发展到第二代解决了内核态和用户态上下文切换代价较高的问题; 第二代发展到第三代解决了软件转发平台性能不足的问题。 第一...

云网络诞生

1. 产业竞争核心 - 芯片 - 操作系统 - 应用 2. 转发能力发展路线 虚拟交换机发展从10Gbit/s(以原始openvswitch为代表的内核态转发,使用 kernel 作为 datapath) -> 25Gbit/s(以dpdk技术为依托的内核态转发,使用用户空间作为 datapath)...

1. Vscode配置C++开发环境

1. 插件安装 C/C++ 2. 安装gcc 3. 配置C++环境 C++环境需要.vscode 文件夹下的以下三个文件共同定义 - ccppproperties.json :对C/C++扩展的设置。 - tasks.json :定义如何生成可执行文件。 - launch.json :定义如何调试可...

2. 基本语法

1. 程序入口 在 C++ 标准中, 函数是程序启动后调用的第一个用户定义函数,它有两种标准的定义形式: 1. :这种形式表示 函数不接受任何命令行参数。它向调用者返回一个整数值,用于表示程序的退出状态。通常,返回值 表示程序正常结束,非零值表示程序出现异常或错误。 2. :这种形式允许程序接收命令...

3. 命名空间

命名空间(namespace)是 C++ 中一项重要的特性,它可以将全局作用域划分为不同的部分,从而避免不同库或者不同模块之间的命名冲突。标准 C++ 库中的所有标识符(像类、函数、对象等)都被定义在了 命名空间里。 1. using namespace std 在没有使用 时,若要使用标准库中的标...

4. 变量定义与内存空间分配

C++ 中定义变量时,即使不进行初始化,内存空间通常也会被分配,不过不同类型的变量在分配内存空间的时机和默认初始值方面存在差异。 1. 局部变量(自动存储期) 局部变量一般定义在函数内部或者代码块内部,具有自动存储期。当程序执行到定义局部变量的语句时,会立即为其分配内存空间,但不会自动初始化,该内存...

5. String字符串

c++包含两种风格的字符串 - C 风格字符串 - C++ 引入的 string 类类型 1. char类型 C风格的字符串起源于C语言,并在C++中继续得到支持。字符串实际上是使用null字符\0终止的一维字符数组。因此,一个以null结尾的字符串,包含了组成字符串的字符。 2. 字符串函数 | ...

Code

Go Env

1. GOROOT与GOPATH - 环境变量表示 Go 语言的安装目录。在中,的默认值是,而在或中的默认值是,如果将Go安装在其他目录中,而需要将GOROOT的值修改为对应的目录。另外,则包含Go为我们提供的工具链,因此,应该将配置到环境变量 PATH 中,方便我们在全局中使用 Go 工具链。 -...

Go Mod

从 Go 1.11开始,Go引入了Go Modules作为官方的依赖管理解决方案。在Go Modules模式下,依赖项会被下载到目录下,命令默认会将可执行文件安装到目录下。文件位于项目根目录下,用于记录项目的模块信息和依赖项及其版本,一个典型的例子如下: 1. go mod命令 2. module ...

Go

Go命令行参数

- os.Args变量是一个字符串(string)的切片(slice) - os.Args的第一个元素,os.Args[0], 是命令本身的名字;其它的元素则是程序启动时传给它的参数

Channel

1. 声明和初始化 1.1. 仅声明(未初始化) 声明一个 int 类型的 channel,但未初始chnilpanicmake()make()ch := make(chan int)chch := make(chan int, 3)3varvar ch chan int = make(chan i...

Context

go1.7加入了一个新的标准库context,用来简化对于处理单个请求的多个goroutine之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个API调用。 fff88f">当一个上下文被取消时,它派生的所有上下文也被取消。当context生命周期结束时,可以从管道接收消息,子协...

Defer

1. 关键字 defer 用于注册延迟调用。 2. 这些调用直到return前才被执。因此,可以用来做资源清理。 3. 多个defer语句,按先进后出的方式执行。 4. defer语句中的变量,在defer声明时就决定了,语句中的fff88f">函数调用参数会在语句执行时立即求值,并保存下来。这意味...

Goto与Label

Go语言也支持label(标签)语法:分别是和 、。 - break label可以跳出label层级循环 - continue label可以从label继续下一次循环 - goto可以无条件的跳转执行的位置,但是不能跨函数,需要配合标签使用 执行结果 输出结果

函数

1. 结构体返回值 在Go语言中,函数返回结构体时,接收它的变量类型可以是值类型或指针类型 1.1. 使用值类型接收值类型返回值 - 结构体较小(如 2-3 个字段的结构体),直接复制不会有明显性能影响。 - 结构体的数据不需要在外部修改,返回值是独立的副本。 输出如下 1.2. 使用指针类型接收指...

变量赋值&结构访问

1. 取地址与解引用 是取地址符号,放到一个变量前使用就会返回相应变量的内存地址。如 是指针运算符,一个指针变量指向了一个值的内存地址。放到一个变量前可以对指针解引用,获取指针指向的实际变量。 输出如下 2. 变量赋值 go中直接对结构体进行复制,会进行值拷贝。 结构体的指针属性也会拷贝,但指针指向...

基本语法

基础

1. 变量初始化 go中使用var声明的变量会自动初始化,如下是等价的 2. array 1. 数组:是同一种数据类型的固定长度的序列。 2. 数组定义:,比如:,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。 3. 长度是数组类型的一部分,因此,和是不同的类型。 4. 数组可以通过...

并发

异常处理

在Go语言中,和都是用来处理错误情况的,但是它们的使用场景和应用场合是不同的。 1. panic 更适用于程序出现不可恢复的错误情况,例如违反了一些重要的前提条件、发生了一些严重错误等。一般来说,如果程序出现了,就意味着程序已经处于一个不可控的状态,无法继续执行下去。会导致程序立即终止并打印出错误堆...

循环与Select

1. select 语句会从上到下依次检查每个 的通信操作语句,每个case必须是一个通信操作,要么接收,要么发送。 - 如果发现某个 的通信操作可以立即执行,就会执行该 语句块并跳出 代码块。 - 如果多个 均可执行,则会随机选择一个执行。 - 如果没有任何一个 可以执行,则会执行 语句块(如果存...

类型断言

类型断言提供了访问接口值底层具体值的方式。 该语句断言接口值 保存了具体类型 ,并将其底层类型为 的值赋予变量 。若 并未保存 类型的值,该语句就会触发一个 panic。 为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。 t, ok := ...

结构体

常用Tool

1. golps golps 是LSP(Language Server Protocol)的一个语言端(Server)实现,是针对 Go 语言的LSP实现。定义了在编辑器或IDE中与语言服务器之间使用的协议,该语言服务器提供诸如自动完成,转到定义,查找所有引用等语言功能。语言服务器索引格式(LSIF...

Kafka

Redis

1. simple example 2. redis pool

Web

测试

Golang单元测试对文件名和方法名,参数都有很严格的要求。 1. 文件名必须以xxtest.go命名 2. 方法参数必须 3. 使用go test执行单元测试 在文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。 | 类型 | 格式 | 作用 | | ---- | ----------...

Benchmark

Go语言标准库内置的testing测试框架提供了基准测试(benchmark)的能力,能让我们很容易地对某一段代码进行性能测试。 本文中会对斐波那契数列进行基准测试 创建一个基准测试用例 - Benchmark测试文件必须以filename加结尾 - 方法名必须以开头 - 测试参数必须为 1. 执行...

Go性能分析

golang中性能调试优化的方法包括: - benchmark:基准测试,对特定代码的运行时间和内存信息等进行测试 - profiling: 程序分析,程序的运行画像,在程序执行期间,通过采样收集的数据对程序进行分析 - Trace:跟踪,在程序执行期间,通过采集发生的事件数据对程序进行分析 > [...

Pprof

1. 使用方式 必须在代码里引入才能使用,不像Java里jdk工具包中的 、、、、 工具可以单独使用。可以从以下两个包中引入 Golang pprof的使用方式主要有两种 1. 在程序中通过http接口的方式暴露相应的pprof的采集控制界面,需要依赖, 使用 包来进行封装。 2. 可以用来产生du...

程序调优

进程、线程与协程

1. go中线程的数量 Go 使用Goroutine 调度器 (Scheduler) 来管理Goroutine的执行。调度器的核心概念如下 1.1. GMP模型 goalng采用特有的GMP模型。 1. G(Goroutine):指的是 Go 代码中的 Goroutine。 2. M(machine...

Go面向对象

1. 自定义数据类型 增强代码可读性 2. 方法接收器 只有自定义类型(或内置类型)才能够绑定方法,从而获得面向对象的特性。例如,为 绑定 方法。,使得User对象实现了Name方法。 在Go语言中,接口的实现是隐式的,也就是说只要一个自定义类型实现了接口中声明的所有方法,那么它就被认为实现了该接口...

反射

1. 任意类型 在 Go 语言中, 常被称为“空接口”,它的确能承载fff88f">任意类型的值,但这种“万能”特性背后存在明显的权衡和局限性。也可以用关键词替代, go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息 对于这个例子, 的类型信息是 ,数据信息是 ,这两部分信息都是存储...

泛型

1. 泛型 我们知道,函数的 形参(parameter) 只是类似占位符的东西并没有具体的值,只有我们调用函数传入实参(argument) 之后才有具体的值。那么,如果我们将形参 实参这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,问题就迎刃而解:在这里我们将其称之为 类型形参(typ...

Http连接池

1. 长连接 长连接利用keep-alive技术实现,能在多次 HTTP 之间重用同一个 TCP 连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等)。 然而长连接并非没有弊端,天下没有免费的午餐,如果客户端在接收完所有的信息之后还没有关闭连接,则服务端相应的资...

Java

Java9 Java17新特性

1. 集合 增加 了 List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。 2. 私有方法 java8中允许接口有默认方法,java9中允许接口有默认私有方法 3. Optioal的ifPresentOrElse 4. 类型推断 类型...

Jni

会在指定的所有目录下查找名为 libnativememoryutils.so(Linux)或 nativememoryutils.dll(Windows)的文件。 > [!note] > 在 Linux 下,文件名必须是 libnativememoryutils.so,不能省略 lib 前缀。 1....

Mtls实现

1. 生成证书和密钥库 1.1. openssl配置 openssl中可以通过--config指定完整的配置文件,包含所有配置段(sections),用于生成包含扩展字段的证书签名请求(CSR)或自签名证书。 1.2. 生成客户端证书和服务端证书 2. 本地安装ca证书和客户端证书 windows可...

Velocity

1. 新建module-a 引入maven依赖 自定义Annotation 继承AbstractProcessor,实现自定义Processor 2. 新建module-b 添加依赖module-A 类填写注解,以生成编译后的class。可以添加些属性,用于编译class时使用。也可以给多个类添加注...

内存管理

1. 总结 java应用的内存主要分为三块 1. jvm管理的堆内存 2. jvm管理的非堆内存 3. 非jvm管理的内存 1.1. JVM管理的堆内存(Heap Memory) 存储对象实例,是 Java 内存管理的核心区域,由所有线程共享。 - 新生代(Young Generation):存放新...

虚拟线程

1. 概念 1.1. 平台线程 我们常用的Java线程与系统内核线程是一一对应的,系统内核的线程调度程序负责调度 Java线程。为了增加应用程序的性能,我们会增加越来越多的Java线程,而由于多种因素,平台线程的数量受到了很大的约束。 - 资源有限导致系统线程总量有限,进而导致与系统线程一一对应的平...

Grpc

1. 工具推荐 - apifox:rpc客户端,可以导入proto文件,自动生成rpc客户端并重试 2. 通信过程 3. 抓包 4. protocol buffers Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数...

Rpc

4+1视图

| 视图类型 | 描述 | | :-------------- | :------------------------------------------------------------------------------------------ | | 逻辑视图 | 逻辑视图面向系统逻辑分析和...

DDD

DDD理论与实践

1. 什么是DDD DDD是一种处理高度复杂领域的ff0000">设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD不是架构,而是一种架构ff0000">设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域...

核心概念

通用模型 NAT网关模型 1. 领域 汉语词典的解释:领域是从事一种专门活动或事业的范围 、部类或部门。 百度百科的解释:领域具体指一种特定的范围或区域。 两个解释有一个共同点——范围,领域的核心重点落在ff0000">”域”字上,用来确定范围的,fff88f">范围即边界 ,这也是DDD在设计中不...

Design

代码坏味道

1. 重复代码 2. 长函数 3. large class 4. Divergent Change(发散式变化) 对程序进行维护时, 如果添加修改组件, 要同时修改一个类中的多个方法,那么这就是 Divergent Change。举个汽车的例子,某个汽车厂商生产三种品牌的汽车:BMW、Benz和La...

设计原则与典型架构设计模型

架构的设计本质都是为了高内聚、低耦合 1. SOLID原则 1.1. S单一职责原则(single responsibility) 一个class应该只做一件事,一个class应该只有一个变化的原因,核心是ff0000">功能特性解耦和ff0000">高内聚性。避免一个类承担两个特性,修改A特性的时...

1. 为什么要Dpdk(Data Plane Development Kit)

1. 基于OS内核转发的劣势 1.1. 数据路径长,协议栈处理开销大 - 内核路径: 数据包需经过内核协议栈(如TCP/IP栈)的多层处理(链路层→网络层→传输层→应用层),涉及大量内存拷贝、校验和计算、上下文切换等操作。 - DPDK优化: 直接绕过内核,在用户空间处理数据包,省去协议栈的逐层解析...

ConfigMap

1. 创建 1.1. 通过kubectl命令行创建 1.1.1. --from-file参数从文件中进行创建 其中key=是可选的,默认key就是文件名,通过key=可以指定key。 1.1.2. --from-file参数从目录中进行创建 目录下每个配置文件名都被设置为key,文件的内容设成为va...

Ingress Gateway

在Kubernetes的服务网格架构中,Ingress Gateway是通过Envoy实现的。在Istio中,Ingress Gateway 是用于管理进入服务网格的流量的组件,它基于 Envoy 代理构建。Envoy 是一个高性能的代理,专门设计用于处理服务到服务的通信,支持动态服务发现、负载均衡...

Kube Proxy和Istio Envoy

- 每个节点安装了一个kube-proxy - 每个pod以sidecar的形式部署一个envoy - kube-proxy拦截的是进出kubernetes节点的流量,envoy拦截的是进出pod的流量。 - istio作为服务网格的一种实现,本质上提供了应用间的流量、安全管理和可观察性。 1. k...

Kubernetes 核心概念

kubernetes中的基本概念和术语大多是围绕资源对象来说的,而资源对象在总体上可以分为以下两类: (1)某种资源的对象,例如节点(Node),Pod,服务(Service),存储卷(Volume) (2)与资源对象相关的事物与动作,例如标签(Label)、注解(Annotation)、命名空间(...

Pod调度

k8s Master上的Scheduler服务负责实现Pod调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通过我们无法知道Pod最终会被调度到哪个节点上。 1. NodeSelector 可以实现将Pod调度到一些指定的Node上,可以...

Service

一个service对外暴露一个,client可以访问这个来实现负载均衡访问后端Pod。一个service对应的“后端”由pod的ip和端口号组成,这在kubernetes中称为endpoint。一组endpoints形如,其中和都是podIp。

Postgresql DML

1. 常用运维sql

性能优化

1. 查看数据库CPU有没有冲高,一般是有慢SQL 2. 查看数据库I/O有没有冲高,如果有一般是用了外部排序 3. 优化外部排序有两个思路,一个是添加索引,避免临时排序;第二个是限制数据规模,尽量保证在内存中完成排序 4. 先联表后排序,索引会无法利用;要做利用索引做子查询排序,然后再联表 5. ...

数据库关键配置

1. socketTimeout 未配置socketTimeout时,应用不会超时,会永远等待。当出现网络隔离故障时,服务端访问数据库的连接会hang住,导致需要等到tcp重传失败或者keepalive失败才会断开连接,故障时间会非常长。 数据库连接池中的连接可能处于如下两种状态 - 连接处于繁忙状...

数据库理论

1. 三大范式 - 第一范式(1NF) 保证列的原子性,一张表的某个列不能有多个值,不能出现重复列。实际操作中我们可能会违反这个约束,如将列值存成一个json字典,但是我们一定要保证这些值至少是一起读或写的,不存在经常部分更新的场景。 - 第二范式(2NF): 取消部分依赖,表中的每个字段都与主键相...

Etcd

Etcd常见用法

1. 数据put和get 1.1. 设置key和value 1.2. 获取指定key 1.3. 获取前缀下所有key 2. 数据过期与续约 2.1. 创建租约并设置 TTL(Time To Live) 创建一个 TTL 为 60 秒的租约: 该命令会返回一个租约ID,例如 2.2. 查看所有leas...

Etcd架构

etcd 集群通过Raft算法实现了 “动态主从 + 分布式共识” 的架构,确保数据强一致性和高可用性。etcd 集群在同一时刻只能有一个主节点(Leader),且写操作必须由该主节点处理,但读操作可以从任意节点执行。etcd集群中的节点在正常运行时通过Raft算法动态选举 “领导者(Leader)...

Watch使用与原理

Watch机制是etcd的核心功能之一,它允许客户端监听指定键或前缀的变化,实现配置变更的实时通知。默认情况下,watch是从ff0000">最新版本开始监听的。 1. 使用方式 1.1. 基本使用 添加或修改key etcd提供了多种方式使用Watch功能 1.1.1. 命令行方式 1.1.2. ...

数据存储

1. 数据存储格式 etcd的数据存储格式主要是基于键值对的形式。 - 键:是一个唯一的字符串,用于标识存储的数据,类似于字典中的键。例如,在一个配置管理系统中,键可能是某个服务的名称加上配置项的名称,如。 - 值:可以是任意类型的数据,如字符串、整数、JSON 对象等,这取决于具体的应用场景。例如...

Kafka Stream

1. 流式计算和批计算 1.1. 流式计算 流式计算是持续地从数据源获取数据,并实时地对数据进行处理和分析。 1.1.1. 特点 - 实时性强,能够在数据到达时立即进行处理。 - 数据无边界,是持续产生的 - 计算模型有状态,需要维护中间状态,一般用增量计算代替全量计算 - 时延敏感,需要低延迟处理...

Kafka架构

1. topic kafka将消息以topic为单位进行分类,一个topic就是一个逻辑队列。类比于数据库中的分库。 2. partition 为了实现扩展性,提高并发能力,kafka将一个逻辑队列(topic)划分为多个partition,每个partition保存一个topic中的部分数据,每一...

Kafka核心配置项

这里我们以网络服务用的最多的kafka组件为例,来介绍下其实现原理,已经如何在知晓原理的情况下用好kafka。 1. producer - : 控制producer端开启幂等和事务能力。开启幂等后能够保证消息不重复。 - 为生产者提供了一个唯一的事务标识符。通过这个标识符,Kafka 能够跟踪和管理...

Kafka生产消息

1. kafka获取partition对应节点 通过元数据获取 - Kafka producer 在启动时会向 Kafka 集群中的任意一个 broker(通常是配置中的 bootstrap.servers 列表中的一个)发送元数据请求(Metadata Request)。这个请求用于获取集群的元数...

Kafka高可靠

1. 生产可靠 为保证Producer发送的数据,能可靠地发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送ACK(ACKnowledge 确认收到)。如果Producer收到ACK,就会进行下一轮的发送,否则重新发送数据。 1....

Kafka高性能读写

核心: - 顺序读写 - page cache,避免每次读取磁盘。也正是因为顺序读写,避免了大量page cache miss,可以充分利用page cache。 - 零拷贝。 1. 存储结构 一个topic下多个partition,每个partition使用一个文件夹存储。partition命名规...

不同消息队列对比

- RocketMQ 的存储结构相对较为灵活,可以针对不同的场景进行优化。它采用了单一的 commitlog 文件来顺序存储所有消息,然后通过索引文件快速定位特定topic的消息。这种设计使得在处理大量 topic 时,存储资源的分配更加高效。每个topic都对应独立索引文件,加上page cach...

云时代的消息中间件

消息队列核心

- 为什么需要消息队列 - 消息队列的核心设计和使用 - 深入消息队列高性能原理 cap原则 1. 为什么需用消息队列 1.1. 业务解耦 业务解耦,代码解耦。现在的云网络服务大都遵循SDN设计思想,将服务拆分成管控转三部分,不同组件负责承载不同的业务,其解耦方式之一就是通过消息队列。很多消息队列都...

Middleware

Redis

Redis关键参数

1. go-redis 在go- redis中,连接设置是通过 Options 结构体来管理的。 1.1. 单机模式 - Network - 网络类型,可以是 tcp 或 unix,默认值为 tcp。 - Addr - Redis 服务器的 host:port 地址。 - Dialer - 创建新网...

Redis命令

1. redis支持数据类型 - 字符串 - hash(key-value) - list(有序列表) - set(无序唯一集合) - zset(有序唯一优先级队列,每个元素关联一个分数,按从小到大排列) - Bitmap - HyperLogLog,占用内存很小(12kb)的情况,可以用于估算接近...

Redis批处理

1. pipeline Redis执行一条命令需要经历以下过程:、、、。由于Redis本身是基于协议(停等机制)的,虽然Redis已经提供了像 、 这种批量的命令,但是如果某些操作根本就不支持或没有批量的操作或者需要连续执行好几个不同命令,那我们就只能一条一条地执行命令,每执行一条命令都要消耗请求与...

Redis高可用

1. master和slave数据复制机制 在Redis集群模式下,Master和Slave之间默认采用异步复制 - 客户端向Master写入数据后,Master立即返回成功响应,无需等待Slave确认。 - 数据随后异步复制到 Slave 节点。 这种设计的目的是保证高性能和可用性,但存在数据丢失...

代码示例

发布订阅模式

Redis发布订阅简介 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis的SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。 作为例子, 下...

Zookeeper

分布式锁

1. 分布式公平锁 ZooKeeper的临时顺序节点,天生就有一副实现分布式锁的胚子。 1. ZooKeeper的每一个节点,都是一个天然的顺序发号器。 在每一个节点下面创建临时顺序节点(EPHEMERAL\SEQUENTIAL)类型,新的子节点后面,会加上一个次序编号,而这个生成的次序编号,是上一...

基本概念

1. zookeeper数据存储 ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如 /hbase/master,其中 hbase 和 master 都是 ZNode。每个 ZNode 上都会保存自己的数据内容,同时...

DCN

DCN架构演进

1. 服务与网络三大耦合问题 - 交换机堆叠,存在单点风险和运维困难问题 - 物理网络大二层隧道,引入更大故障域 - 硬件墙的引入限制整体云业务规模,硬件强性能不行,无法横向扩展 2. 名词解释 NSA(Network Service Area) - NWS(Network Service): 网络...

交换机堆叠

1. 背景 主机连接单个交换机,存在单点故障问题(单个交换机故障),解决tor交换机单点故障的思路有两个。 - 上层服务(计算、网络)保证高可用,当一个tor下主机故障时,可以快速在另一台tor下拉起新主机 - 两台tor交换机堆叠,保证tor交换机的高可靠 2. 概念 交换机堆叠一般是指被背板堆叠...

大二层架构的问题

1. 历史背景 网关在汇聚上的原因是因为openstack平面约束,要求资源池里面的节点必须在一个二层上。 管理服务区解大二层原因是管理区容灾1.0方案依赖vip在az间漂移,管理区汇聚下就是一个二层。同时,az之间还需要在两个az的汇聚交换机上通过硬件vxlan打一个二层隧道。 2. 大二层的问题...

(七)Linux下实现NAT

在NAT Overview一文中,我们介绍了在linux下通过iptables实现NAT功能的命令及原理。本文以容器为例,进行几个简单的实验来进一步了解linux下NAT的实现过程和应用。 1. nat在容器中的应用 1.1. SNAT linux下一个最典型的NAT应用就是docker容器借助宿主...

(九)高性能NAT

(五)NAT ALG

--- title: NAT ALG date: 2023-02-01 categories: - network tags: - network - nat --- 能够对特定的应用层协议进行转换,在对这些特定的应用层协议进行转换过程中,通过的会话信息来改变封装在报文载荷中的和端口信息,最终实现下...

(八)高可靠NAT

1. 双机冷备 2. 双机热备 双机热备可以极大地提升NAT服务的可靠性和承载能力,但是两台NAT设备上的NAT配置也需要完全相同。这样就会出现一个问题:如果两个NAT设备分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了优先级的概念。 在双机热备的环境...

(六)路由器配置NAT

本章节主要以cisco路由器配置为例,使用GNS3仿真平台搭建一个最简单的实验环境进行验证。 1. 常用命令(cisco) 首先介绍本实验可能用到的一些命令,先有个概念,后面再在实验中熟悉并掌握。 1.1. VPC 1.2. 路由器 2. 环境 2.1. 配置PC 2.1.1. PC1 2.1.2....

(十)NAT穿透

NAT高可靠

1. 双机热备 双机热备对于防火墙来说是一个必不可少的功能,两台防火墙上的NAT配置也需要完全相同,这样就会出现一个问题:如果两个防火墙分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了地址池优先级的概念。 在双机热备的环境中,如果地址池被配置为高优先级...

流日志

1. 格式 1.1. 网络信息7元组 internalip,internalport,externalip,externalport,transitip,transitport,protocol 1.2. 流量数据 packetnum,bytesize 1.3. 时间信息 starttime,end...

Network

IP分片报文和TCP分段报文

分组可以发生在传输层和网络层,传输层中的TCP会分段,网络层中的IP会分片。IP层的分片更多的是为传输层的UDP服务的,由于TCP自己会避免IP的分片,所以使用TCP传输在IP层都不会发生分片的现象。我们在学习TCP/IP协议时都知道,TCP报文段如果很长的话,会在发送时发生分段,在接受时进行重组,...

SCTP

SCTP ( Stream Control Transmission Protocol ),即流媒体控制传输协议,是一种可靠的基于无连接数据包网络如IP网络之上传输协议。他被设计用来在IP网络上传输PSTN在窄带信令消息,同时也能支持宽带信令消息的传输。 SCTP可以看作OSI层次结构中的传输层,它...

Tcp KeepAlive

1. 起源 连接有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接。长连接环境下,双方建立交互的连接并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现: 1. 主机突然掉电、死机、异常重启 2. 中间路由...

Tcp重传

1. tcp超时重传 重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的确认应答报文,就会重发该数据,也就是我们常说的超时重传。 TCP 会在以下两种情况发生超时重传: - 数据包丢失 - 确认应答丢失 1.1. RTT与RTO - RTT(Roud Tri...

Nat封装Pp协议

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements client ^pdsoIL0z NAT ^lEtOOR5s Server ^7HyKnicB SYN seq0 ^3...

源地址透传

1. TOA(TCP Option Address) TOA将源地址放在字段中。option字段最长40字节,每个选项由三部分组成:op-kind、op-length、op-data,我们最常见的MSS字段就是在option里。目前option使用的op-kind并不多,我们只需要构建一个不冲突的o...

DHCP协议

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,统一使用两个IANA分配的端口:67(服务器端),68(客户端),DHCP客户端使用的源端口号为68,目的端口号为67发送请求消息到...

Dns Zone

DNS Zone是指域名系统(DNS)中管理的一部分域空间,包含了该区域内所有域名的记录信息。它是确保网络中计算机能够通过域名解析到正确 IP 地址的关键组成部分,对于维持互联网的正常运行至关重要。一个DNS Zone通常由一个组织或个人负责维护,这个区域包括了域名及其子域的权威信息,如IP地址、邮...

DNS

DNS配置与匹配规则

1. multi dns ip 对于一个主机配置多个DNS IP 1.1. 作用 1.1.1. 冗余和容错 - 当第一个DNS服务器无法响应时,主机会自动尝试使用第二个、第三个DNS IP - 提高DNS解析的可靠性,即使一个DNS服务器宕机,仍可通过其他DNS服务器进行域名解析 1.1.2. 负载...

Https抓包

由于主流浏览器都只支持HTTP/2 Over TLS,也就是说当前HTTP/2网站都使用了HTTPS,数据传输都经过了SSL加密,常规抓包方法并不能看到明文数据。 1. wireshark Wireshark 的抓包原理是直接读取并分析网卡数据,要想让它解密 HTTPS 流量,有两个办法: 1)如果...

HTTP协议解析

1. HTTP 1.0 !http1.0抓包.pcapng HTTP/1.0 默认为每一对 HTTP 请求/响应都打开一个单独的 TCP 连接。 - 17945-17947:tcp三次握手 - 17948:Server告诉Client更新自己的接收窗口大小 - 17949:Client发起HTTP ...

TLS

1. TLS是什么 Transport Layer Security (TLS) 是一种被广泛采用的安全协议,旨在增强互联网通信的私密性和数据安全性。TLS的主要使用场景是对Web应用和服务器之间的通信(例如,Web 浏览器加载网站)进行加密。TLS 还可以用于加密其他通信,如电子邮件、消息传递和 ...

常用Http Header

1. remoteaddr 表示发出请求的远程主机的IP地址,remoteaddr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时 1. 假设中间没有任何代理,那么网站的web(,Apache等)就会把remoteaddr设为你的机器IP 2...

应用层

ARP

1. arp响应的条件 1.1. 普通主机响应 ARP 请求的条件 当本机是普通主机时,只有当 ARP 请求中的目标IP地址与本机IP地址匹配时,普通主机才会发送ARP响应。例如,主机A的IP地址为192.168.1.10,当它接收到ARP请求,且请求的目标 IP地址为192.168.1.10时,主...

VLAN

1. 介绍 当网络上所有设备在同一个广播域产生大量的广播和多播帧(arp/dhcp/stp/rip),就会与业务数据流争带宽,造成网络性能恶化。将大型广播域分段是提高网络性能的方法之一。路由器能够将广播包阻隔在一个三层接口上,但是路由器的LAN接口数量有限,它的主要功能是在三层网络间传输数据,而不是...

交换机原理

1. SAT(MAC地址表、FDB、CAM) 交换机的Source Address Table(源地址表),也称为MAC地址表、FDB(forwarding database)或CAM(Content Addressable Memory),是一种用于存储和管理设备MAC地址和端口之间映射关系的表格...

概念解释

ISP: Internet Service Provider 互联网服务提供商 IXP: Internet eXchange Point 互联网交换点 通信方式: - 客户-服务器方式(C/S) - 对等方式(P2P) 1. 电路交换 电路交换必须经过“建立连接(占用 通信资源)一通话(一直占用通信...

IPSec

IPSec (Internet Protocol Security) 协议主要工作在OSI模型的第3层网络层,为上层传输层协议(如TCP、UDP等)提供安全支持。IPSec不是一个单独网络协议,而是一系列为IP网络提供安全性的协议和服务的集合,提供了认证、加密、数据完整性等安全服务。 1. 主要协议...

仿真

Ipv4地址

1. 特殊ip地址汇总 - 0.0.0.0/8:用于广播信息到当前主机 - 10.0.0.0/8:用于专用网络中的本地通信 - 172.16.0.0/12:用于专用网络中的本地通信 - 192.168.0.0/16:用于专用网络中的本地通信 - 100.64.0.0/10:用于在电信级NAT环境中服...

IPv6地址

1. 地址格式 IPv6地址有128bit,在这128bit中,前64bit是网络前缀,后64bit是接口标识。在前64bit中,前48bit又是全球可汇总地址,在给一个公司分配IPv6地址时,总是分配给它一个前48bit固定的地址,而后面的16bit又可以被该公司用来做子网地址。这样分配,可以方便...

网络层

链路本地地址

链路本地地址(Link-local Address)是一类特殊的IP地址,仅用于在网段内、同一广播域内的主机相互通信使用,这类主机可认为是不需要外部互联网服务的。其中IPv4的链路本地地址定义在169.254.0.0/16范围内,IPv6定义在fe80::/10范围。链路本地地址在ipv4和ipv6...

BGP

BGP基础概念

1. AS - OSPF、IS-IS等IGP路由协议在组织机构网络内部广泛应用,随着网络规模扩大,网络中路由数量不断增长,IGP已无法管理大规模网络,AS的概念由此诞生。 - AS指的是在同一个组织管理下,使用统一选路策略的设备集合。 > [!question] 不同的AS之间需要进行通信,在AS之...

BGP邻居建立

- 先启动BGP的一端先发起TCP连接,R1先启动BGP,R1使用随机端口号向R2的179端口发起TCP连接,完成TCP连接的建立。 - 三次握手建立完成之后,R1、R2之间相互发送Open报文,携带参数用于对等体建立 - 参数协商正常之后双方相互发送Keepalive报文,收到对端发送的Keepa...

路由协议

CPU

1. 基本概念 - Socket: 指的是主板上用于安装物理CPU芯片的接口。每一个这样的接口能够插入一个物理的CPU处理器,一台计算机可以配备多个这样的插槽,进而安装多个物理CPU,以增强系统的计算能力和性能。一个物理CPU可以有多个物理CPU核。 - 物理核(): 可以看的到的,真实的cpu核,...

File System

常见文件目录

各大发行版基本一致,遵循的是通用的FHS(Filesystem Hierarchy Standard)约定,(个别子目录如 lib64、multiarch 名称可能略有不同) 1. 开发者部署模板 用一个假想应用myapp为例,展示其在Linux系统中应该如何分布文件和目录。 🏗️ myapp 部...

Linux

Bridge

1. 常见命令 2. 泛洪 - 泛洪机制:桥接设备会将该数据包从除接收端口之外的所有其他端口发送出去。这样做的目的是确保目的设备无论连接到哪个端口,都有机会接收到这个数据包。例如,在一个包含多个主机的局域网中,如果主机 A 向一个桥接设备发送了一个数据包,而该桥接设备的 FDB 表中没有目的主机的 ...

Geneve隧道配置

1. geneve协议格式 GENEVE与VXLAN类似,仍然是Ethernet over UDP,也就是用UDP封装Ethernet。VXLAN header是固定长度的(8个字节,其中包含24bit VNI),与VXLAN不同的是,GENEVE header中增加了TLV(Type-Length...

Network Namespace基础

可以创建一个完全隔离的新网络环境,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables等等。总之,与网络有关的组件都是独立的。 可以看到我们刚才创建的网络环境 进入虚拟网络环境,使用命令 只能看到lo口 这样我们可以在新的网络环境中打开一个shell。 连...

Route Table与VRF

在 Linux 系统中,支持使用多个 route table(路由表) 是为了增强网络配置的灵活性和功能性。多路由表的主要作用如下: 1. 实现策略路由(Policy Routing) 默认情况下,Linux 使用主路由表()中的路由规则来决定流量的转发路径。然而,某些场景下可能需要基于流量的源地址...

Tcp关键内核参数

1. tcpsynretries 这个参数值设置的是client发送SYN如果server端不回复的话,ff0000">重传SYN的次数。对我们的直接影响就是connet建立连接时的超时时间。当然Java通过一些C原生系统调用的组合使得我们可以进行超时时间的设置。在Linux里面默认设置是5,下面给...

Tun Tap介绍

在计算机网络中,TUN(Tunnel)与TAP(Test Access Point)是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。 - TAP等同于一个以太网设备,它操作第二层数据包...

Vxlan隧道配置

1. 主机应用通过绑定独立路由表或vrf的方式通过隧道访问对端 > 图为网图,本文配置vtep地址换成了10.0.0.250和10.0.0.251 250主机配置 查看当前系统信息 > 虽然从概念上讲,VRF 主要用于实现逻辑上的路由隔离,但在系统的网络配置和管理中,它被呈现为一个类似普通网络接口的...

管道

1. client 2. server 3. ouput 数据直接在内存中的管道缓冲区传输

网卡混杂

网卡在混杂模式下不会校验收到的报文的mac和ip

CPU使用率

1. 查看进程CPU使用率 1.1. 平均使用率 ps命令显示的值是进程的平均CPU使用率,计算公式为: $\%CPU=(进程使用的CPU时间​/进程运行的总时间)×100$ 如下命令显示CPU占用最高的前几名进程 输出如下 1.2. 瞬时使用率 如果想监控某个进程(比如 )的 CPU 使用率 ...

Ls

1. 基于文件名排序 2. 基于文件大小排序 3. 基于文件时间排序

Lsof

(List Open Files)是一个用于列出系统中打开文件的工具。这里的 “文件” 是一个广义的概念,包括常规文件、目录、块设备、字符设备、共享库、网络套接字等。会列出系统中当前用户打开的所有文件。 : 列出指定进程 ID 的进程打开的文件 :列出指定用户打开的文件 : 列出正在执行指定命令的进...

PS

命令是process status的简称,用于显示当前运行的进程的信息。在不使用任何标识的情况下,会显示所有当前用户启动的进程,比如: - PID: 进程的ID号 - TTY: 终端名称缩写 - TIME: CPU时间,即进程使用CPU的总时间 - CMD: 所执行的命令名称。 1. 参数 命令支持...

Sort

-f :忽略大小写的差异,例如 A 与 a 视为编码相同; -b :忽略最前面的空格符部分; -M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法; -n :使用『纯数字』进行排序(默认是以文字型态来排序的); -r :反向排序; -u :就是 uniq ,去重,相同的数据中,仅出现一...

常用运维命令

核隔离和Cgroup

!CPU1. 基本概念 1. 核隔离 修改内核启动参数 1.1. CPU加压测试 htop可以看到隔离的核没有受到影响 2. 绑定进程到隔离核心 3. cgroup 输出如下 > [!info] > Worker 0 started on CPU 2 Worker 2 started on CPU ...

Numa架构

是一种多处理器架构,它将系统中的内存分成多个节点,并将每个节点分配给不同的处理器。在 NUMA 架构中,每个处理器可以访问本地节点的内存,但访问远程节点的内存速度较慢。因此,NUMA 架构可以提高多处理器系统的性能和可扩展性。 下图为英特尔S2600系列服务器主板 - 两个CPU插槽,CPU插槽之间...

Os

Page Cache

文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被CPU访问。由于读写硬盘的速度比读写内存要慢很多,所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux内核使用页缓存(Page Cache)机制来对文件中的数据进...

中断

1. 中断(Interrupt)的定义 中断是计算机系统中一种关键机制,允许处理器暂停当前执行的任务,转去处理更高优先级的紧急事件,处理完成后恢复原任务。中断机制提高了系统的响应速度和资源利用率,是操作系统实现多任务、实时响应的基础。 2. 硬件中断(Hardware Interrupt) vs 软...

操作系统地址空间

操作系统的地址空间是一个抽象概念,表示操作系统在运行时能够管理和访问的内存范围。它提供了一种将物理内存映射到逻辑地址的机制,使得程序能够在一个独立于物理硬件的虚拟地址空间中执行。 1. 用户空间与内核空间 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所...

操作系统概述

1. 操作系统基本特征 1.1. 并发 并发是指两个或多个事件在同一时间间隔内发生,在多道程序环境下,一段时间内宏观上有多个程序在同时执行,而在同一时刻,单处理器环境下实际上只有一个程序在执行,故微观上这些程序还是在分时的交替进行。操作系统的并发是通过分时得以实现的。操作系统的并发性是指计算机系统中...

进程调度策略

进程调度的关注点: - 性能:执行完所有任务需要的总时间。 - 公平性:每个任务都期望自己先被执行,尽早完成。 - 响应时间:首次运行时间 - 任务提交时间。 性能、公平性、响应时间是矛盾的,为了达到最好的性能,我们希望cpu不要在不同进程或线程之间切换,这样避免了上下文的切换,一个任务从开始执行到...

Openssl

1. RSA密钥 生成私钥 - :指定算法为 RSA - :指定密钥长度为 2048 位(可改为 4096 - :输出私钥文件路径 生成公钥 - :输入私钥 - :导出公钥 - :输出公钥文件路径 2. ECC密钥 生成对应的ECC私钥 - :指定椭圆曲线(可换成 , , , 等) - :生成密钥 ...

SSL证书

在密码学和数字证书体系中,一般没有“私钥证书”的说法。主要是公钥证书。公钥证书(通常就简称为数字证书)是一种电子文档,包含以下信息 - 公钥信息 - 持有者身份信息(如姓名、组织等) - 证书颁发机构(CA)的数字签名等 其主要目的是将公钥与特定的实体(如个人、服务器等)绑定,用于在网络通信等场景中...

Webauthn

在数字时代,密码已成为我们日常生活和在线活动中不可或缺的一部分。尽管互联网已经发展了20多年,许多方面都有了巨大的改进,但只有密码,还是20年前的用法。更准确的说,它的用户体验比 20 年前更差了。 1. 密码的强度现在要求越来越高,一般不能少于8个字符,还要包括特殊符号。 2. 除了密码,通常还有...

Tech

CPU架构

1. 主频 CPU 主频,即CPU的时钟频率,是衡量 CPU 运算速度的重要指标,以下从其定义、工作原理及与性能的关系等方面进行具体解释。 - 基本定义:CPU 主频表示 CPU 内数字脉冲信号振荡的速度,单位为赫兹(Hz)。通常所说的CPU 主频为2.4GHz、3.6GHz等,意味着CPU内部的时...

CPU缓存设计

1. 缓存 Cache Memory也被称为Cache,是存储器子系统的组成部分,存放着程序经常使用的指令和数据,这就是Cache的传统定义。从广义的角度上看,Cache是快设备为了缓解访问慢设备延时的预留的Buffer,从而可以在掩盖访问延时的同时,尽可能地提高数据传输率。 快和慢是一个相对概念,...

CPU调度策略

1. MLFQ(Multi-Level Feedback Queue) - 系统中存在多个就绪队列,每个队列有不同的优先级,高优先级队列中的进程会优先获得 CPU 执行。 - 进程在队列之间可以移动,根据其行为(如 CPU 占用时间、I/O 操作等)动态调整优先级。 1.1. 规则 1.1.1. 优...

Blog Online

TODO

- BGP实践,BGP PEER建立 - 核隔离与CGROUP - NUMA - 中断 - 硬件卸载 - 大页 - etcd 1. cloud wan ccn一个vpc支持创建几个attachment ccn路由同步,会同步到未关联的attachment吗?默认会同步所有路由表吗?

Icloud

使用土区账号购买50G icloud,平均每个月2.5块 1. 土区账号登录icloud 2. 国区账号登录apple store 3. 闲鱼购买土区礼品卡充值到土区账号 4. 土区账号购买icloud 5. 过去账号通过airdop发送邀请土区账号加入家庭组 6. 土区账号共享icloud设置家庭...

Excel

1. 将多个sheet join合并 Power Query自动化合并(适合多列/大数据) 1. 导入数据到Power Query: - 在Excel中点击数据 > Power Query > 获取数据,加载Sheet1和Sheet2。 2. 合并查询: - 选择Sheet1作为主表,与Sheet2...

Vnc远程桌面

总览

Clash For Linux

Jupyter安装

z从sqlite官网,下载安装最新版本sqlite3 清理旧版本的sqlite3 安装并编译python环境 配置jupyter lab 添加python环境 1. 格式转换

Movie Pilot

1. docker 安装 注意要通过环境变量配置代理,否则无法访问themoviedb或thetvdb等 2. 下载与整理 可以通过软连接目录的方式达到下载目录和mp的/media目录一致。 3. ref https://wiki.movie-pilot.org/zh/install

Movie Bot

NAS搭建

1. 主机硬件 - 主板:微星B360M Mortar - CPU:i3-8300t - 内存:金士顿骇客8G 2 DDR4 - SSD:三星980 NVMe PCIe3.0 500G - 西数:红盘4T - 散热器:利民AXP90 X47 - 电源:台达VX350 - 机箱:先马米立方matx 2...

Openwrt

Ubuntu系统备份

1. 备份 2. 还原 如果当前启动无法启动,可以通过live cd来启动并执行恢复操作

Xiaoya

windows下必须使用bridge模式 1. docker安装 2. 定时清理 3. 获取元数据 在wsl的ubuntu子系统中执行 4. 参考

Advanced Table

obsidian 功能 - 自动化格式表格 - Excel样式的表格导航,即使用Tab和Enter在行和列之间导航 - 对指定的列进行函数求值 - 添加、删除、移动行和列 - 设置列的对齐方式 - 对指定列进行排序 - 将表格导出为CSV格式 公式 基本格式如下: 如中的 代表最后一行、第二列,右边...

Appearance

打开笔记仓库的 .obsidian 文件夹,其中如果没有 snippets 文件夹则创建。在 Obsidian 中的 CSS snippets 都是以 .css 的档案格式储存在特定的文件夹。如果你有将 CSS snippets 放到该文件夹,就会在 Obsidian 显示开关。 1. 别人给你的,...

Dataview

1 查询依据 yaml数据/metainfo 2 使用查询语言 使用下列语法创建查询语言代码块 dataview query command 3 使用内联查询 内联查询的结果只能是一个,不能查询一个列表。当前页面可以通过获得,其他页面可以通过双链语法获得 通过下列语法创建内联查询: 此博客文件名: ...

Excalidraw

obsidian 是一个手工风格的白板工具。可以使用呼出命令菜单,输入excalidraw进行创作。 library 提供了很多公开的模板库可以帮助我们画出很多精美的图案。 导出 可以导出为png或svg 双链 鼠标右键选中create link,可以在excalidraw中使用双链。和在markd...

Image Auto Upload Plugin

obsidian 使用aliyun oss作为obsidian图床 1. 下载 2. 配置oss作为图床 3. 在obsidian中粘贴图片后自动上传aliyun os

Mind Map

obsidian 1. 使用方式 使用呼出命令行,输入通过提示补全命令 2. pin 可以将思维导图的预览面板嵌到当前笔记中。 3. copy screenshot 将svg格式mind map复制到剪切板 4. bug修改 mind map已经很久没有维护了,对于代码块支持存在bug,会导致无法生...

Minimal Theme Setting

obsidian 设置主题 settings -> options -> appearance里面选择主题 Style settings 自定义包括字体等各种样式 Minimal Theme setting 里面内置了一些经典的配色,可以对主题进行一些快速设置

Obsidian Tasks

任务管理 常用语法 1. 今日之前(包括)已完成,done before 2. 本周截止当前日(包括)已完成,done after 3. 根据重要性排序,sort by priority reverse 示例 获取本周所有已经完成的任务,按优先级倒序排列 tasks done before done...

Obsidian总览

obsidian 1. 视图 obsidian一共提供了三种视图: - preview mode:预览模式,可以编辑,显示实时预览结果,此模式下metadata不会渲染 - reading mode:阅读模式,markdown渲染后结果,不可编辑 - source mode:以纯文本形式显示mark...

代理

ssh代理

效率工具

1. 添加环境变量 2. windows命令行代理 3. git代理 也可以直接编辑/.gitconfig文件 4. windows配置beyondcompare作为gitdiff 修改.gitconfig配置文件 5. 删除重复文件

Vault

Tcp异常断链

1. 现象 客服访问建行业务偶现超时。 建行server端主动发起断链,发送fin报文,客户B收到后回ACK报文,进入fin-wait-2状态 客户B服务端前设置有防火墙,fin状态下设置10秒超时,客户B超时后的rst报文被防火墙丢了,建行server无法收到rst报文,导致一直处于fin-wai...

业务偶现超时

1. 现象 客户A通过NAT网关访问客户B业务偶现超时。 2. 抓包分析 中间设备抓包发现,服务端主动断链,发送FIN报文并收到ACK报文完成两次挥手,但客户端并没有随之进行被动关闭,直到约20秒后发出rst报文,在此期间连接一直处于半关闭状态。而NAT网关fin超时时间为5秒,此时会话已经老化,再...

交换机选型要点

交换机选型

clos架构

CLOS网络的核心思想是:用多个小规模、低成本的单元构建复杂,大规模的网络。简单的CLOS网络是一个三级互连架构,包含了输入级,中间级,输出级。下图中的矩形都是规模小得多的转发单元,相应的成本也很低。简单来说,CLOS就是一种多级交换架构,在输入输出增长的情况下,增加中间交换单元的数量,而不是每个交...

NAT分片报文

前文我们聊到,在NAT地址转换中,NAT除了对IP地址转换外,还使用到TCP或UDP报文的端口号、ICMP报文的ICMP头中的identifier字段信息。当一个IP分片报文和TCP分段报文 1|IP报文被分成若干片之后,这些信息只有首片报文会携带,后续分片报文依靠报文ID、分片标志位、分片偏移量依...

NAT之ICMP

报文没有类似于或的端口信息,而正常NAT为了内网的安全性和IP地址的高利用率,一般都使用了五元组来匹配NAT会话。为了对ICMP的请求报文进行NAT映射,又要确保网络的安全性,只允许合法的ICMP响应报文进入内网,就必须对其进行特殊的处理。 1. ICMP请求和应答报文 ICMP的request和r...

NAT会话

我们知道传输层的任意一条流都是通过两个建立的,由组成,因此一条流可以用五元组表示。这个五元组中的任意一个元素都不能改变,否则就是另外一条流了。对于服务端来说,和中任意一个变了,就意味着一个新的接入连接;而对于客户端来说,和中任意一个发生变化,访问的就是一个新的服务,比如通常是一个服务,是一个数据库的...

Thrift协议

是一种轻量化、语言无关的RPC框架。主要包含三大部分:代码生成、序列化框架、RPC框架。 Thrift网络协议栈 采用的是模型,网络协议栈从下到上分别为:、、、。 Transport 传输层为网络提供了一个简单的抽象,定义了具体的网络传输协议。其本质是一个提供了通过各种协议进行通信的框架,支持包括和...

shell编程

本文主要介绍shell编程的基本语法以及实际应用中的常见命令。 1. 注释 1.1. 单行注释 1.2. 多行注释 多行注释也可替换成或 2. 变量 2.1. 变量定义 使用的形式,VALUE如果是字符串的话,可以使用单引号、双引号或者不加引号。单引号内的任何字符都会原样输出,不能进行转义,单引号内...

netstat命令

是基于Netstat这个命令行工具的指令,它可以用来查询系统上的网络套接字连接情况,包括,以及。另外它还能列出路由表,接口状态和多播成员等信息。 1. 参数选项 | 参数 | 作用 | | | --- | -----------------------------------------------...

系统信息查看命令汇总

在系统下经常要查看各种信息,需要使用到各种命令,全部背下来也是一项不小的工作量。每次临时找也比较浪费时间,这里汇总下常用的命令。 1. 系统 1.1. 查看linux内核版本 文件系统不是普通的文件系统,而是系统内核的映像。也就是说,该目录中的文件是存放在系统内存之中的,它以文件系统的方式为访问系统...

数据库隔离级别

本文主要目的是阐明数据库的四种隔离级别以及在mysql下实现隔离的原理。 1. 隔离级别 数据库事务隔离级别从低到高分别是:read uncommitted、read committed、retpeatable read、serializable,依次解决了数据库脏读、不可重复读、幻读问题。 - 脏...

用户和权限

使用 Linux 系统,不免会和用户和权限打交道,本文介绍了根权限和文件的权限属性等概念和应用。 1. 用户与群组 使用查看所有用户 使用查看所有用户组 修改文档所有者或群组 2. Linux权限 命令 - 代表三种身份owner/group/other,a代表全部身份all - 代表三种操作行为(...

进程管理

在 系统中,进程是资源调度的最小单位,进程的管理关乎着你使用系统的体验。 1. 进程类型 Linux 系统里有几种不同类型的进程:用户进程(User processes)、守护进程(Deamon processes)和内核进程(Kernel processes)。 1.1. 用户进程 系统里大多数进...

NAT概览

1. 背景 地址使用4个字节进行存储,最多能够提供个地址。随着互联网尤其是物联网的发展,全球地址早已不够用,因此人们发明了(网络地址转换)来缓解这个问题。 简单来说,国际互联网组织划分了三个网络地址段作为内部网络本地通信使用,分别是,,。大部分内部机器都使用这些网段中的私有地址,如果它们需要访问公网...

NAT Overview

1. What is NAT? 1.1. NAT(Level 4) NAT(Network address translation)即网络地址转换,工作在OSI模型的三层或四层(PNAT),用于修改IP数据包中的IP地址和端口。当在专用网内部的一些主机本来已经分配到了local ip地址,但又想和I...

iptables

iptables 是 Linux 防火墙工作在用户空间的管理工具,是 netfilter/iptablesIP 数据包过滤系统是一部分,用来设置、维护和检查 Linux 内核的 IP 数据包过滤规则。 1. 四表五链 数据包在经过每个的时候会按照每个链对应的表依次进行查询匹配执行的操作,如PRERO...

tcpdump

tcpdump是一个优秀的网络分析工具,提供了强大且简单的接口。 1. options - -i any 监听所有的网卡接口,用来查看是否有网络流量 - -i eth0 只监听eth0网卡接口 - -D 显示可用的接口列表 - -n 不要解析主机名 - -nn 不要解析主机名或者端口名 - -q 显...

数据中心网络架构

0.1. 数据中心 根据维基百科释义,指用于安置计算机系统及相关部件的设施,例如电信和存储系统。数据中心是全球协作的特定设备网络,用来在internet网络基础设施上传递、加速、展示、计算和存储数据信息。 0.2. 传统数据中心网络架构 如图1所示,传统的大型数据中心网络通常采用三层架构。cisco...

应用层DNS协议

(Domain Name System)域名解析服务采用架构,是一个应用层协议。的作用是将人类可读的域名(如:www.shinerio.cc) 转换为机器可读的 IP 地址(如:1111.111.111.111)。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。协议建立...

计算机网络(九)—— 传输层

传输层架构在网络层之上,在两台计算机进程之间传输数据,常见的传输层协议包括TCP和UDP。 1. TCP 1.1. 首部格式 1.2. TCP状态机 TCP是面向连接的,在其生命周期会有各种不同状态 | 状态 | 描述 | | ------------ | ---------------------...

路由协议

路由是选择路径并将报文沿着选择的路径进行转发的过程。 1. 路由器 1.1. 路由器功能 路由器从功能上可以划分为: - 路由选择:使用路由协议(或手工静态配置的方式)获取整个网络的拓扑结构,构造、维护路由表。 - 分组转发:分组转发结构由交换结构、一组输入端口和一组输出端口构成。 1. IP分组检...

计算机网络(七)—— 网络层 —— ICMP

互联网控制消息协议(Internet Control Message Protocol,ICMP)是TCP/IP协议族的核心协议之一,用于IP协议中发送控制消息,提供可能发生在通信环境中的各种问题反馈。通过这些消息,使管理者可以对所发生的的问题作出诊断,然后采取适当的措施解决。ICMP可以简单认为是...

数据链路层概述

数据链路层使用的信道主要分为以下两种: - 点对点信道,使用一对一的点对点的通信方式 - 广播信道,使用一对多的广播通信方式,广播信道上连接的主机很多,因此必须使用专用的共享信道协议来协调这些主机的数据发送。 0.1. 数据链路 当需要在一条线路上传送数据时,除了必须有一条物理线路外,还必须有一些必...

计算机网络(三)—— 网络的基本分类

本文主要介绍计算机网络的分类以及局域网技术。 0.1. 网络分类 - 地理位置: 1. 个域网(PAN,Personal Area Network)。个域网允许设备围绕一个人进行通信。一个常见的例子是计算机通过无线网络(蓝牙)与其外围设备(显示器、键盘、鼠标。打印机)连接。 2. 局域网(LAN,L...

计算机网络(二)—— 性能指标

计算机网络的常见的性能指标有速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率。 1. 速率(bit/s或byte/s) 速率是物理层概念,指的是信道上每秒钟传输的0/1比特数量。比如下载文件中显示的7.8MB/s指的是每秒中可以传输7.8M字节的信息。 2. 带宽(bit/s) 带宽是逻辑概念...

计算机网络(一)—— 分层模型

计算机网络按照使用范围可以划分为个域网、局域网、城域网、广域网和互联网,其组成复杂,存在着大量诸如集线器、交换机、路由器、笔记本、手机等硬件。为了降低网络设计的复杂性,现阶段大部分网络都会形成一个层次栈结构,每一层都建立在其下一层的基础之上,向上一层提供特定服务。这种分层结构的概念其实和计算机领域的...

加密算法

本文主要介绍密码学中常见的两种加密算法—对称加密和非对称加密。 1. 散列(摘要)算法 在学习加密算法之前,我们先来了解一下散列算法(散列不是加密)。散列算法是通过一定方式对原文进行计算,产生一个哈希值,不管原始数据是什么样的,得到的哈希值都是固定长度的,其作用只是为了验证数据的完整性和唯一性,无法...

© 2026 shinerio. All rights reserved.

} | Remove-Item -Force\n```"},{"id":"Vault","title":"Vault","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":0,"slug":"vault","description":"","relativePath":"Vault.md","rawContent":""},{"id":"tcp异常断链","title":"Tcp异常断链","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":1,"slug":"tcp异常断链","description":"1. 现象 客服访问建行业务偶现超时。 建行server端主动发起断链,发送fin报文,客户B收到后回ACK报文,进入fin-wait-2状态 客户B服务端前设置有防火墙,fin状态下设置10秒超时,客户B超时后的rst报文被防火墙丢了,建行server无法收到rst报文,导致一直处于fin-wai...","relativePath":"问题定位/tcp异常断链.md","rawContent":"# 1. 现象\r\n客服访问建行业务偶现超时。\r\n\r\n建行server端主动发起断链,发送fin报文,客户B收到后回ACK报文,进入fin-wait-2状态\r\n\r\n客户B服务端前设置有防火墙,fin状态下设置10秒超时,客户B超时后的rst报文被防火墙丢了,建行server无法收到rst报文,导致一直处于fin-wait-2状态,直到5分钟超时。\r\n\r\n客户A发起新的http请求,由于NAT网关端口分配机制,导致分配到了相同的端口,syn报文到达服务端后,处于该状态下的socket只能收数据,不能发数据。"},{"id":"业务偶现超时","title":"业务偶现超时","date":"2025-09-17T13:31:12.000Z","tags":[],"readingTime":2,"slug":"业务偶现超时","description":"1. 现象 客户A通过NAT网关访问客户B业务偶现超时。 2. 抓包分析 中间设备抓包发现,服务端主动断链,发送FIN报文并收到ACK报文完成两次挥手,但客户端并没有随之进行被动关闭,直到约20秒后发出rst报文,在此期间连接一直处于半关闭状态。而NAT网关fin超时时间为5秒,此时会话已经老化,再...","relativePath":"问题定位/业务偶现超时.md","rawContent":"# 1. 现象\r\n客户A通过NAT网关访问客户B业务偶现超时。\r\n\r\n# 2. 抓包分析\r\n中间设备抓包发现,服务端主动断链,发送FIN报文并收到ACK报文完成两次挥手,但客户端并没有随之进行被动关闭,直到约20秒后发出rst报文,在此期间连接一直处于半关闭状态。而NAT网关fin超时时间为5秒,此时会话已经老化,再次建链,端口没有被分配出去,仍可以重用同一端口。\r\n\r\n# 3. 状态分析\r\n客户B作为server端主动断链,发送fin报文,客户A作为client端收到fin报文后回ACK报文,客户B进入fin-wait-2状态,客户A进入close_wait状态。客户A发出rst报文后,server端应该立即释放连接,但实际并没有。后来抓包发现客户B和客户A之间有防火墙,fin状态下会话超时时间为10秒。由于客户B的rst报文,被防火墙丢了,客户B server无法收到rst报文,导致一直处于fin-wait-2状态,直到5分钟超时。\r\n\r\n# 4. 结论\r\n客户A发起新的http请求,由于NAT网关端口分配机制,导致分配到了相同的端口,syn报文到达服务端后,处于该状态下的socket只能收数据,不能发数据。因此一直timeout。\r\n\r\n1. 优化nat端口分配逻辑,随机分配端口\r\n2. 客户端代码发现bug,未主动close"},{"id":"交换机选型","title":"交换机选型要点","date":"2025-02-20T00:00:00.000Z","tags":["交换机"],"readingTime":8,"slug":"交换机选型","description":"交换机选型","relativePath":"Tech/Network/DCN/交换机选型.md","rawContent":"---\r\ntitle: 交换机选型要点\r\nsource: https://cloud.tencent.com/developer/article/1909154\r\ncreated: 2025-02-20\r\ndescription: 交换机选型\r\ntags:\r\n - 交换机\r\n---\r\n**交换机选型要点:**\r\n**(1)制式 (盒式交换机/框式交换机)**\r\n**(2)功能(二层交换机/三层交换机)**\r\n**(3)端口数量**\r\n**(4)端口带宽**\r\n**(5)交换容量**\r\n**(6)包转发率**\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/03551acec6e3dd48599995b0cb100ae9.png)\r\n\r\n# 1. 制式\r\n当前的交换机主要分为盒式和框式。\r\n\r\n盒式交换机样例图:\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/8a0e733c195bb1c85e4e2eb516380777.png)\r\n\r\n框式交换机样例图:\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/6c42bd86fc196025f16202502351916a.png)\r\n\r\n## 1.1. 盒式交换机\r\n盒式交换机皆可以理解成一个铁盒子,一般情况下盒式交换机是固定配置,固定端口数量,固定电源模块、风扇等;因此盒式交换机不具备扩展性。为了提高扩展性,盒式交换机可以支持堆叠技术,可以将多台盒式交换机逻辑上组成一台交换机。正常情况下,盒式交换机应用在一个网络的接入层或者汇聚层。利用clos架构,最新的DCN组网在核心上也可以使用盒式交换机。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/7328262ceb19e3205f821dd4c3c876f2.png)\r\n\r\n\r\n## 1.2. 框式交换机\r\n\r\n框式交换机基于机框,接口板卡、交换板卡、电源模块等都可以按照需求独立配置,框式交换机的扩展性一般基于槽位数量。框式交换机一般应用在一个网络的核心位置。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/defd5c784904b6d922c43cf26d0725bc.png)\r\n\r\n如上图组网所示:数据中心网络中,CE5800、CE6800、CE8800都是盒式设备,一般作为接入层使用;CE128是框式设备,一般作为核心层使用。\r\n\r\n因此,在设备选型的时候可以根据实际交换机的使用层级判断选择盒式交换机还是框式交换机。\r\n\r\n# 2. 功能\r\n\r\n## 2.1. 交换机按照工作协议层分类\r\n\r\n交换机可以分为二层交换机和三层交换机。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/227da6d646a9e0cbba71e04e7fc66844.png)\r\n\r\n## 2.2. 二层交换机、三层交换机区别\r\n\r\n### 2.2.1. 二层交换机\r\n工作在OSI参考模型的第二层数据链路层上交换机,主要功能包括物理编址、错误校验、帧序列以及流控。(如下图所示,二层交换机工作在数据链路层,可以处理数据帧)\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/18c0e0990ad85aaf42d28a4458f55461.png)\r\n\r\n## 2.3. 三层交换机\r\n一个具有三层交换功能的设备,即带有第三层路由功能的第二层交换机,但它是二者的有机结合,并不是简单地把路由器设备的硬件及软件叠加在局域网交换机上。(如下图三层交换机工作在网络层,可以处理数据包)\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/e77efb287fc64bdbe0eeef451dc0b114.png)\r\n\r\n# 3. 端口数量\r\n\r\n## 3.1. 盒式交换机\r\n一台交换机可以提供的端口数量,对于盒式交换机每一种型号基本是固定的,一般提供24个或48个接入口,2-4个上连接口。这里以华为CE5850-48T4S2Q-EI为例(如下如所示),一共有48个1000M接入口,4个10G上行口,2个40G上行口;\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/88901306b01e65460aa76087d420ed1c.png)\r\n\r\n## 3.2. 框式交换机\r\n框式交换机则跟配置的单板数量有关,一般指配置最高密度的接口板的时候每个机框能够支持的最大端口数量。\r\n这里以华为的CE12804为例,支持4块业务板LPU,端口和具体的单板型号相关,我们以36端口100G单板为例,那么插满单板一共有144个100G端口。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/8b448e1ea85249a6640d98ec5023cd04.png)\r\n\r\n## 3.3. 如何根据端口数量选择一款交换机\r\n在选择交换机时需要基于当前的业务情况,和未来的可扩展性,交换机端口数量代表你需要接入的终端数量。以一个48个接入口的交换机为例,那么如果1个终端占用一个端口,那么一台交换机就可以接48个终端,如果是一个200人的公司,那么就需要这样的交换机5台。\r\n\r\n> [!note]\r\n> 框式交换机时延比较高\r\n\r\n# 4. 端口速率\r\n当前交换机提供的端口速率有100Mbps/1000Mbps/10Gbps/25Gbps等。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/7fc2e8d5f7c3e832daf046929dacaade.png)\r\n\r\n## 4.1. 交换机端口速率单位\r\n交换机的端口速率的单位是bps(bit per second),即**每秒多少比特。**\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/47fca0fc3bdadf7f388eefac83f7f802.png)\r\n\r\n# 5. 交换容量\r\n交换机交换容量:也称为背板带宽或交换带宽。交换容量是交换机接口处理器(或接口卡)和数据总线之间所能吞吐的最[大数据](https://cloud.tencent.com/product/bigdata-class?from_column=20065&from=20065)量。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/41fa4db635e1a651cbc3d8ec7d8200f0.png)\r\n\r\n背板带宽标志了交换机总的数据交换能力,单位为Gbit/s。一台交换机的交换容量越高,所能处理数据的能力就越强,但同时设计成本也会越高。所有端口容量端口数量之和的两倍应该小于交换容量,从而实现全双工无阻塞交换\r\n\r\n交换容量跟交换机的制式有关,对于总线式交换机来说,交换容量指的是背板总线的带宽;\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/daee3a548965479288d8dacd3a5ba6c5.png)\r\n\r\n对于交换矩阵式交换机来说,交换容量是指交换矩阵的接口总带宽。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/1a0a563aba09ba972cea3363cb1864a4.png)\r\n\r\n这个交换容量是一个理论计算值,但是它代表了交换机可能达到的最大交换能力。当前交换机的设计保证了该参数不会成为整台交换机的瓶颈。\r\n\r\n# 6. 包转发率\r\n包转发率也称为接口吞吐量,是指通信设备某接口上的数据包转发能力,单位通常为pps(packet per second)。交换机的包转发率一般是实测的结果,代表交换机实际的转发性能。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/12ad6986362d1b65dcb928cc9a952b78.png)\r\n\r\n## 6.1. 计算方式\r\n包转发率的衡量标准是以单位时间内发送64字节的数据包(最小包)的个数作为计算基准的。当计算包转发率时,需考虑前导码和帧间隙的固定开销。\r\n\r\n缺省情况下,帧间隙为最大值12字节,建议用户使用缺省配置。如果用户修改接口的帧间隙为较小值,则接收端在接收一个数据帧以后,可能会没有充足的时间接收下一帧,导致无法及时处理转发报文而出现丢包现象。\r\n\r\n![](https://ask.qcloudimg.com/http-save/yehe-8611941/f64a36d1de19609e363e9bff0ed4c587.png)\r\n\r\n我们知道以太帧的长度是可变的,但是交换机处理每一个以太帧所用的处理能力跟以太帧的长度无关。所以,在交换机的接口带宽一定的情况下,以太帧长度越短,交换机需要处理的帧数量就越多,需要耗费的处理能力也越多。"},{"id":"clos架构","title":"clos架构","date":"2025-02-19T00:00:00.000Z","tags":["dcn","network"],"readingTime":4,"slug":"clos架构","description":"CLOS网络的核心思想是:用多个小规模、低成本的单元构建复杂,大规模的网络。简单的CLOS网络是一个三级互连架构,包含了输入级,中间级,输出级。下图中的矩形都是规模小得多的转发单元,相应的成本也很低。简单来说,CLOS就是一种多级交换架构,在输入输出增长的情况下,增加中间交换单元的数量,而不是每个交...","relativePath":"Tech/Network/DCN/clos架构.md","rawContent":"---\r\ntitle: clos架构\r\ndate: 2025-02-19\r\ncategories:\r\n - network\r\ntags:\r\n - dcn\r\n - network\r\n---\r\nCLOS网络的核心思想是:用多个小规模、低成本的单元构建复杂,大规模的网络。简单的CLOS网络是一个三级互连架构,包含了输入级,中间级,输出级。下图中的矩形都是规模小得多的转发单元,相应的成本也很低。简单来说,CLOS就是一种多级交换架构,在输入输出增长的情况下,增加中间交换单元的数量,而不是每个交换单元的端口数,从而尽可能减少中间的交叉点数。\r\n\r\n传统$n*n$交换网络,需要$n^2$个交叉点。可以看出,这里的开关矩阵类似于一块布的纤维,所以交换机内的架构被称为 Switch Fabric(纤维),这是 Fabric 成为计算机网络专业术语的起源。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202502192342186.png)\r\n\r\n下图整列有N=36个输入和N=36个输出。有3个交换阶段,也就是一个输入阶段(a)一个中间阶段(b)和一个输出阶段(c)。在a阶段有6个$6*11$交换机,在b阶段有11个$6*6$交换机,在c阶段有6个$6*11$交换机。交叉点的数量是为$6*11*6 + 11*6*6 + 6*11*6=1188$个交叉点,少于$36*36=1296$个交叉点。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/202502192343363.png)\r\n> [!tip]\r\n> 1. stage(a)和stage(b),stage(b)和stage(c)之间是端口直连的方式,没有交叉点\r\n> 2. 多个小型化的模块单元组网实现了等效大模块的组网\r\n\r\n在a阶段我们交换机的输入36,因此就有6个有6个输入的交换机。相同的方式,c阶段就有6个有6个输出的交换机。阶段b的交换机的数量必须在最糟糕的情况下保持非阻塞。最糟糕的情况如下:\r\n(1)a中的一个交换机的5个输出端口很忙,需要占用连接到b阶段的5个交换机(每个输出端口连接到了不同的交换机)\r\n(2)同时,c中一个交换机的5个输入端口很忙,也会占用连接到b阶段的5个交换机\r\n(3)极端情况下这10个b阶段的交换机都正好不一样\r\n(4)a阶段和c阶段剩余的一个端口要建立连接,则需要额外提供一个交换机,因此b阶段需要有11个交换机\r\n(5)因此a节点需要11个输出分别连接到11个b阶段交换机上,c节点也需要11个输入分别连接到11个b阶段交换机\r\n\r\n总结clos三层架构需要的交叉点数为\r\nstage(a)和stage(c):$\\sqrt{n}*(2\\sqrt{n}-1)*\\sqrt{n}$,例如$6 * 11 * 6$\r\nstage(b): $\\sqrt{n}*\\sqrt{n}*(2\\sqrt{n}-1)$,例如$6*6*11$\r\n总计:$C(3)=(2\\sqrt{n}-1)*\\sqrt{n}*\\sqrt{n} * 3$\r\n\r\n> [!note]\r\n> [在N大于等于36的情况下,交叉点的数量就会小于N的平方](https://blog.csdn.net/xtydtc/article/details/134488259)\r\n\r\n# 1. reference\r\n[设计非阻塞电话交换系统:多阶段交叉点整列的研究与优化-CSDN博客](https://blog.csdn.net/xtydtc/article/details/134488259)"},{"id":"(三)NAT分片报文","title":"NAT分片报文","date":"2023-03-12T00:00:00.000Z","tags":["network","nat"],"readingTime":5,"slug":"三nat分片报文","description":"前文我们聊到,在NAT地址转换中,NAT除了对IP地址转换外,还使用到TCP或UDP报文的端口号、ICMP报文的ICMP头中的identifier字段信息。当一个IP分片报文和TCP分段报文 1|IP报文被分成若干片之后,这些信息只有首片报文会携带,后续分片报文依靠报文ID、分片标志位、分片偏移量依...","relativePath":"Tech/Network/NAT/(三)NAT分片报文.md","rawContent":"---\r\ntitle: NAT分片报文\r\ndate: 2023-03-12\r\ncategories:\r\n- network\r\ntags:\r\n- network\r\n- nat\r\n---\r\n\r\n前文我们聊到,在NAT地址转换中,NAT除了对IP地址转换外,还使用到TCP或UDP报文的端口号、ICMP报文的ICMP头中的identifier字段信息。当一个[[IP分片报文和TCP分段报文 1|IP报文被分成若干片]]之后,这些信息只有首片报文会携带,后续分片报文依靠报文ID、分片标志位、分片偏移量依次关联到前一个分片。\r\n\r\n\r\n\r\n以ICMP分片报文为例,在ICMP报文分片后,只有在首片ICMP报文中包含ICMP头的identifier字段。在首片报文到达NAT设备后,可以按照正常的转换流程,根据源IP地址和identifier信息生成会话表并转发出去。但是在第二个及后续分片到达后,由于只包含IP地址却没有identifier信息,可能因此无法进行NAT转换。解决的办法有两种:\r\n1. 先重组再进行NAT转换,在分片报文到达后,先进缓存,等属于这个IP报文的所有分片到达后进行虚拟分片重组,再进行NAT地址转换。最后将NAT转换完成的IP报文发送出去。\r\n2. 在首片到达并转换后,NAT设备记录并保存转换首片使用的ip及分片identifier信息,并在后续分片到达后应用同样会话表对ip信息进行转换。\r\n# 1. 重组转发\r\n1. 当NAT设备收到一个分片报文时,它会检查该报文是否属于一个已经开始重组的数据包,如果是,就将该报文放入对应的缓存区;如果不是,就为该数据包创建一个新的缓存区,并将该报文放入其中。\r\n2. NAT设备会定期检查缓存区中的分片报文是否已经完整,如果是,就将它们重组成一个完整的数据包,并进行NAT转换,然后将转换后的数据包发送出去;如果不是,就继续等待其他分片报文到达。\r\n3. 如果缓存区中的分片报文在一定时间内没有完整,或者超过了NAT设备能够处理的数量,那么NAT设备会丢弃这些分片报文,并向源主机发送一个ICMP错误消息。\r\n## 1.1. 分片重组失败导致的ICMP查错报文\r\n1. 如果NAT设备丢弃分片报文是因为缓存区已满,那么它会向源主机发送一个ICMP目的不可达消息,类型为3,代码为4,表示分片重组失败。\r\n2. 如果NAT设备丢弃分片报文是因为超时,那么它会向源主机发送一个ICMP超时消息,类型为11,代码为1,表示分片重组超时。\r\n## 1.2. 缺点\r\n分片重组的方式由于需要缓存分片报文,导致增加了NAT设备的内存消耗,延长了分片报文的传输时间。\r\n\r\n# 2. 会话记录ID\r\n正常NAT会话使用五元组`(srcIP,dstIp,srcPort,dstPort,protocol)`来区别不同的流并匹配会话进行转发,分片报文由于没有port和protocol信息,因此可以在会话中添加`identifier`来识别统一条流的不同分片报文以完成后续分片报文的ip替换。"},{"id":"(四)NAT之ICMP","title":"NAT之ICMP","date":"2023-02-19T00:00:00.000Z","tags":["network","nat"],"readingTime":5,"slug":"四nat之icmp","description":"报文没有类似于或的端口信息,而正常NAT为了内网的安全性和IP地址的高利用率,一般都使用了五元组来匹配NAT会话。为了对ICMP的请求报文进行NAT映射,又要确保网络的安全性,只允许合法的ICMP响应报文进入内网,就必须对其进行特殊的处理。 1. ICMP请求和应答报文 ICMP的request和r...","relativePath":"Tech/Network/NAT/(四)NAT之ICMP.md","rawContent":"---\r\ntitle: NAT之ICMP\r\ndate: 2023-02-19\r\ncategories:\r\n- network\r\ntags:\r\n- network\r\n- nat\r\n---\r\n\r\n`PING`报文没有类似于`TCP`或`UDP`的端口信息,而正常NAT为了内网的安全性和IP地址的高利用率,一般都使用了五元组来匹配NAT会话。为了对ICMP的请求报文进行NAT映射,又要确保网络的安全性,只允许合法的ICMP响应报文进入内网,就必须对其进行特殊的处理。\r\n\r\n\r\n# 1. ICMP请求和应答报文\r\nICMP的request和reply报文中包含两字节的ID,用于区分不同的ICMP进程。对于unix以及类unix系统来说,就是ping进程号。ID仅适用于回显请求和应答ICMP报文,对于目标不可达ICMP报文和超时ICMP报文等,该字段的值为0。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200426202102.png)\r\n\r\n根据[[(二)NAT会话]]一文我们可以知道,`TCP`或`UDP`通过五元组`(srcIp,srcPort,dstIp,dstPort,protocol)`来表示一条唯一的流,而对于ICMP报文来说,一条流的唯一性可以由`(srcIp,dstIp,ID)`确认。我们可以为ICMP报文生成特殊的会话格式和会话管理,但显然一个更简单的办法是沿用`TCP`或`UDP`的会话管理模式。`ICMP`协议和`TCP`以及`UDP`协议主要区别在于少了源端口和目的端口。观察到ICMP协议中ID是用来参与标识一条流的,我们很容易想到可以将ID当做是其中一个端口处理,比如源端口(当然目的端口也是可以的,这里只是为了方便会话标记,本身并没有任何协议上的意义)。此外,我们可以将ICMP当成是一种特殊的\"服务\",监听在服务器的某个特殊端口,比如端口0,当然我们也可以认为是1或10000或任意的一个数,这个数并不重要,只是用来补齐icmp协议中的目的端口。这样,ICMP便也有了五元组信息,可以像`TCP`和`UDP`一样来进行会话管理。记:\r\n```c\r\nupKey=(internalIp,icmp_id,externalIp,0,protocol_icmp)\r\ndownKey=(externalIp,0,transitIp,icmp_id,protocol)\r\n```\r\n\r\n# 2. 差错报文\r\n\r\n内网主机通过TFTP协议从外网的主机下载文件,但是外网主机并没有开启TFTP服务,这时外网主机会向内网回应ICMP端口不可达的差错报文。对于源地址转换的NAT来说,外网的报文要想顺利进入内网,就必须五元组匹配设备上的NAT会话表项,内网访问外网时使用的是UDP协议,而ICMP差错报文是ICMP协议,这并不符合NAT设备的映射规则。同时ICMP差错报文也并没有像请求和应答报文一样的ID号可供区分。通过抓包可知,icmp差错报文是根据icmp差错报文数据段中的原始IP数据包进行NAT转换的。\r\n\r\n内网主机访问外网主机时,设备上会生成一个会话表项,内网主机使用的是UDP协议,源IP和源端口为192.168.200.2/2428,目的IP和目的端口为218.197.70.2/69,NAT设备进行NAT转换后的源IP和源端口为218.197.70.12/1048。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/icmp%E6%9F%A5%E9%94%99%E6%8A%A5%E6%96%87%E5%8E%9F%E5%A7%8B%E6%8A%A5%E6%96%87.png)\r\n\r\n外网发到内网的icmp差错报文(NAT前),可以看到原始的UDP报文(经过NAT后的)被封装在了ICMP差错报文的载荷部分。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/icmp%E6%9F%A5%E9%94%99%E6%8A%A5%E6%96%87_NAT%E5%89%8D.jpg)\r\n\r\n外网发到内网的icmp差错错报(NAT后),根据内层的UDP报文匹配会话信息,进行NAT转换:\r\n1. 替换外层的目的ip为内网主机的内网地址\r\n2. 替换icmp内层ip报文,源地址替换为原始报文ip,源端口替换为原始报文端口。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/icmp%E5%B7%AE%E9%94%99%E6%8A%A5%E6%96%87_NAT%E5%90%8E.jpg)\r\n\r\n同时收到ICMP差错报文后可以对NAT会话进行加速老化,节省NAT设备的资源。"},{"id":"(二)NAT会话","title":"NAT会话","date":"2023-02-10T00:00:00.000Z","tags":["nat","network"],"readingTime":14,"slug":"二nat会话","description":"我们知道传输层的任意一条流都是通过两个建立的,由组成,因此一条流可以用五元组表示。这个五元组中的任意一个元素都不能改变,否则就是另外一条流了。对于服务端来说,和中任意一个变了,就意味着一个新的接入连接;而对于客户端来说,和中任意一个发生变化,访问的就是一个新的服务,比如通常是一个服务,是一个数据库的...","relativePath":"Tech/Network/NAT/(二)NAT会话.md","rawContent":"---\r\ntitle: NAT会话\r\ndate: 2023-02-10\r\ncategories:\r\n- network\r\ntags:\r\n- nat\r\n- network\r\n---\r\n\r\n我们知道传输层的任意一条流都是通过两个`socket`建立的,`socket`由`(ip,port,protocol)`组成,因此一条流可以用五元组`(srcIp,srcPort,dstIp,dstPort,protocol)`表示。这个五元组中的任意一个元素都不能改变,否则就是另外一条流了。对于服务端来说,`srcIp`和`srcPort`中任意一个变了,就意味着一个新的接入连接;而对于客户端来说,`dstIp`和`dstPort`中任意一个发生变化,访问的就是一个新的服务,比如通常`80`是一个`http server`服务,`5432`是一个`postgresql`数据库的服务端口。\r\n\r\n`NAT`是用来做地址转换的,为了保证一条流五元组的一致性,我们需要保证`NAT`前和`NAT`后的IP、端口以及协议在一条流的整个生命周期都不变。为了保证这种不变性,我们需要维持一种`NAT`映射关系,我们把这个映射关系称之为`NAT`会话(session)。对于`SNAT`而言,`transtitIp`是被多个内网主机共享的,这个映射关系不能是永久的,否则就变成了内网`(internalIp,internalPort)`和`(transitIp,transitPort)`是`1:1`关系, `(transitIp,transitPort)`的组合关系很快便会出现耗尽的情况。因此对于`SNAT`而言,会话的另一个重要属性就是会话超时时间。而对于`DNAT`而言,由于内网是服务端,对外应该暴露固定的`transitIp`和`transitPort`,且需要被映射到内网特定的某个主机提供的某个特定服务,即一组固定的`internalIp`和`internalPort`。这个映射关系是不随着外网的`externalIp`和`externalPort`改变而改变的,因此这种会话一般是永久性的。`DNAT`的会话是固定映射的,很好理解,因此下文我们讨论的会话主要是针对`SNAT`而言。\r\n\r\n\r\n\r\n举个例子,假设有一条流:\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/SNAT%E6%B5%81.png)\r\n\r\n\r\nNAT前:`TCP 192.168.0.100:5000 -> 120.120.120.120:80`\r\nNAT后:`TCP 110.110.110.110:6000 -> 120.120.120.120:80`\r\n\r\n那么`TCP 192.168.0.100:5000 -> 120.120.120.120:80`到`TCP 110.110.110.110:6000 -> 120.120.120.120:80`的这样一个映射关系就可以称之为会话,而`UDP 192.168.0.100:5000 -> 120.120.120.120:80`到`UDP 110.110.110.110:6000 -> 120.120.120.120:80`则是另一个会话。\r\n\r\n# 1. 术语定义\r\n为了方便下文阐述,我们先统一一下术语:\r\n- 定义一条流中从内网发往外网的流量为**上行流量**,`TCP 192.168.0.100:5000 -> 120.120.120.120:80`就标记了一条流上行流量,我们记为:`upKey=(internalIp,internalPort,externalIp,externalPort,protocol)`\r\n- 定义一条流中从外网发往内网的流量为**下行流量**,`TCP 110.110.110.110:6000 -> 120.120.120.120:80`就标记了一条流下行流量,我们记为:`downKey=(externalIp,externalPort,transitIp,transitPort,protocol)`\r\n> 注意这里我们并没有区分去程流量和回程流量,对于私网主动访问外网的场景,上行流量就是去程流量;对于外网主动访问内网的场景,上行流量就是回程流量。\r\n\r\n此处我们可以导出会话`session = upKey + downKey + expireTime`。\r\n> 注意,这里我们讨论的都是对称型NAT,关于锥型NAT读者可以自行研究,相对来说会话信息会少一些,映射条件相对更宽松些。\r\n\r\n# 2. 会话管理\r\n一个会话的完整生命周期可以分为三个阶段:\r\n- 会话建立\r\n- 会话匹配(续约)\r\n- 会话老化\r\n\r\n## 2.1. 会话匹配(续约)\r\n对于一个上行报文,首先肯定是判断是否已经生成映射关系(session),如果有的话我们只需要按照这个会话进行源地址和源端口的替换就行了,否则才需要新建一个会话。因此在讲会话的建立之前,我们先来聊聊会话的匹配。上行报文可以用一个`upSessKey`来表示,我们需要通过这个`upSessKey`来找到对应的`(transitIp,transitPort,protocol)`的组合。很自然地,我们就想到用一个`map`结构来存储,即一个`upSessKey`到`(transitIp,transitPort,protocol)`的映射关系,定义为`upSessMap`。同样对于下行报文,我们也可以使用一个`downSessKey`到`(internalIp,internalPort,protocol)`的映射关系来表示,定义为`downSessMap`。因此会话的匹配,可以简单概括上行报文通过`upSessKey`在`upSessMap`中匹配获得`session`,下行报文通过`downKey`在`downSessMap`中匹配获得`session`。\r\n\r\n上文我们说过,会话是具有超时时间的,为了保证长连接不中断,`NAT`设备在接收到每一个业务报文完成`NAT`转换的同时,还需要对会话的超时时间进行刷新,以便在业务持续有报文的情况下会话不会老化,这个超时时间的刷新就可以称之为会话续约。\r\n\r\n## 2.2. 会话建立\r\n由于`internalIp`和`internalPort`是由内网主机决定的,`externalIp`和`externalPort`是由业务访问的目的外网主机决定的,而`protocol`是由通信双方的具体业务类型决定的(`TCP`、`UDP`、`icmp`),因此`NAT`设备实际上可以控制的只有`transitIp`和`tranitPort`,因此会话管理本质上管理的是`transitIp`和`tranitPort`的分配以及销毁。对于一个新建会话,为了保证和任何其他的流不冲突,最稳妥的方式就是使用一个新的`(transitIp,transitPort)`的组合,且不同的`protocol`之间的会话是不会冲突的。因此,我们可以定义如下会话分配池:\r\n\r\n```c\r\n// NAT设备控制的一组用于NAT的外网IP\r\nuint32_t transitIpPool[];\r\nstruct SessAllocateKey {\r\n uint32_t transitIp;\r\n uint8_t protocol;\r\n}\r\nstruct BitMap ports;\r\n// key: SessAllocateKey value: BitMap\r\nstruct HashMap *sessAllocateMap;\r\n```\r\n\r\n因此会话分配的逻辑就是通过一定的算法从`transitIpPool`中选出一个`transitIp`,然后从`sessAllocateMap`中获取当前`protocol`下此`transitIp`管理的端口分配情况的`bitmap`,选出一个未占用的`port`标记为占用,生成`upSessKey -> (transtIp,transitPort,protocol)`的映射关系。同时为了使下行流量(`snat`回程流量)能够顺利地找到会话,将下行流量的`dstIp`和`dstPort`替换为原来的`internalIp`和`internalPort`,此时我们应该同时生成`downKey -> (intenralIp,internalPort,protocol)`的映射关系。为了保证会话在`UDP`通信结束或者`TCP`未正常断链的情况下能够被及时回收,此时我们还需要给会话额外添加一个超时时间。最终设计会话结构如下:\r\n\r\n```c\r\nstruct UpSesskey {\r\n uint32_t internalIp;\r\n uint16_t internalPort;\r\n uint32_t externalIp;\r\n uint32_t externalPort;\r\n uint8_t protocol;\r\n}\r\nstruct DownSesskey {\r\n uint32_t transitIp;\r\n uint16_t transitPort;\r\n uint32_t externalIp;\r\n uint32_t externalPort;\r\n uint8_t protocol; \r\n}\r\nstruct Session {\r\n struct UpSesskey upSessKey;\r\n struct DownSessKey downSessKey;\r\n uint32_t expireTime;\r\n}\r\n// key: UpSessKey, value: Session\r\nstruct HashMap *upSessMap;\r\n// key: DownSessKey, value: Session\r\nstruct HashMap *downSessMap;\r\n```\r\n\r\n### 2.2.1. TransitIp选择算法\r\n- Round Robin:不受报文内容的影响,各个`transitIp`上端口的分配比较平均\r\n- Hash:根据五元组进行HASH选择`transitIp`,对于特定的业务流场景,NAT后的IP比较稳定\r\n### 2.2.2. 会话结构优化\r\n由于`upSessKey`和`downSessKey`都包含了`(externalIp,externalPort)`,因此如果在`(externalIp,externalPort,protocol)`组合不同的情况下,`(internalIp,internalPort,protocol)`或`(transitIp,transitPrt,protocol)`的组合是可以一样的,这样也可以保证不同的`upSessKey`、不同的`downSessKey`之间都不冲突,也能定位到会话信息,完成`NAT`。同时`(externalIp,externalPort,protocol)`不同的情况下,对于`NAT`前或`NAT`后的流的五元组都不一样,保证无论是客户端还是服务端`OS`都能识别出来是不同的流。因此我们可以在`(externalIp,externalPort,protocol)`组合不同的情况下,复用`(transitIp,transitPort,protocol)`来提高`NAT`资源的使用率。因此我们的会话分配模型可以优化成如下结构:\r\n```c\r\nuint32_t transitIpPool[];\r\n// 将externalIp和externalPort加到会话分配的冲突域中\r\nstruct SessAllocateKey {\r\n uint32_t transitIp;\r\n uint32_t externalIp;\r\n uint16_t externalPort;\r\n uint8_t protocol;\r\n}\r\nstruct BitMap ports;\r\n// key: SessAllocateKey value: BitMap\r\nstruct HashMap *sessAllocateMap;\r\n```\r\n\r\n## 2.3. 会话老化\r\n### 2.3.1. 超时老化\r\n#### 2.3.1.1. UDP\r\n我们都知道`UDP`协议是面向无连接的,`UDP`会话并没有一个明确的“终结报文”来表示当前`UDP`会话可以被销毁了,因此`UDP`会话的唯一老化手段就是超时老化。`UDP`报文的超时老化时间并没有一个明确的标准,根据业务不同建议设置时间控制在`30s-900s`范围内。如果业务确实需要长时间保留`UDP`会话,建议客户端和服务端之间通过定时心跳进行保活。\r\n\r\n#### 2.3.1.2. TCP\r\n对于`TCP`会话来说,相对就显得比较复杂了。`TCP`是面向连接的,有完整的`TCP状态机`,在`TCP`通信的任何阶段都有可能因为各种网络或主机异常问题导致`TCP`连接被意外释放,而这些意外释放往往不能被`NAT`设备感知,为了保证这种情况下会话能被释放,就需要各种超时老化机制来兜底。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/20230211160124.png)\r\n##### 2.3.1.2.1. sync超时\r\n`TCP`通过三次握手创建一个连接,有以下两种情况可能会导致超时:\r\n- 对于client来说,在发送`syn`后进入`syn_sent`状态,等待server的`syn+ack`\r\n- 对于server来说,在发送`syn+ack`后进入`syn_revd`状态,等待client的`ack`\r\n在linux系统中,默认的syn超时是`75`秒,NAT设备的`syn`超时时间一般应该设置超过这个时间。\r\n```SHELL\r\ncurl -o /dev/null -s -w \"time_connect: %{time_connect}\\ntime_starttransfer: %{time_starttransfer}\\ntime_total: %{time_total}\\n\" 192.168.0.253:5000      \r\ntime_connect: 0.000000\r\ntime_starttransfer: 0.000000\r\ntime_total: 75.007882\r\n```\r\n\r\n##### 2.3.1.2.2. ESTABLISHED超时\r\n`TCP`经历过三次握手后,client和server都进入了`established`阶段。`ESTABLISHED`阶段的异常分为有数据传输和没有数据传输两种情况。\r\n\r\n异常时有数据传输,为了保证`TCP`报文的可靠性,`TCP`提供了[[Tcp重传]]能力,默认情况会下会重传`15`次,最大重传时间120秒,重传15次后(总计约15分钟)后会断开连接,且此时是不会有`FIN`动作的,连接会直接关闭。因为一直存在重传报文,此情况下至少要保证NAT设备的超时重传时间大于单次最大重传时间——2分钟。\r\n\r\n异常时如果没有数据传输,还需要关注`TCP`有没有开启[[Tcp KeepAlive|KeepAlive]]。开启`KeepAlive`功能后,最长可能会需要7875秒(约两个多小时)后才能断开。如果没有开启`KeepAlive`功能,则连接永远不会断开。此时NAT设备也需要设置一个合理的超时时间,保证NAT设备会话功能不受异常影响。\r\n\r\n##### 2.3.1.2.3. FIN超时\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/TCP%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B.png)\r\n\r\n`TCP`通过四次挥手释放一个连接,有以下四种情况都可能会导致超时:\r\n1. 对于主动释放连接的一方来说\r\n\t- 在发送完`FIN`报文进入`FIN_WAIT_1`状态后,等待被动方的`ACK`报文\r\n\t- 在收到被动方的`ACK`报文进入`FIN_WAIT_2`后,等待被动方`FIN`报文\r\n2. 对于被动释放连接的一方来说\r\n\t- 在收到主动方的`FIN`报文并发送`ACK`进入`CLOSE_WAIT`状态后,此时被动方还有数据需要发送给主动方,等待主动方业务`ACK`报文。\r\n\t- 被动方发送`FIN`报文后,进入`LAST_ACK`状态,等待主动方`ACK`报文。\r\n\r\n对于主动方来说,`FIN_WAIT_1`状态下会触发主动方的`TCP`重传(最大重传时间15分钟)后断开连接,而`FIN_WAIT_2`在linux下的默认超时时间为60秒。\r\n```shell\r\n$ more /proc/sys/net/ipv4/tcp_fin_timeout\r\n60\r\n```\r\n\r\n对被动方来说,正常情况下,处于`CLOSE_WAIT`和`LAST_ACK`状态下会触发TCP重传(最大重传时间15分钟)后断开连接。某些情况下,如果被动方因为代码问题(IO阻塞之类的)未及时close socket,未能发出`FIN`报文,此时tcp如果设置了keepalive,会经历最长7875秒后,由内核断开连接。\r\n\r\n### 2.3.2. TCP正常挥手\r\n`TCP`通过四次挥手来完成连接的释放,因此我们可以维护`TCP状态机`,当收到第二个`FIN`报文后又收到`ACK`报文(即`LAST_ACK`),代表`TCP`挥手完成,此时只需要等待`2MSL`后即可释放会话(在被动方没有收到ACK报文的情况下,主动端可以有机会重发)。\r\n\r\n### 2.3.3. TCP RST\r\n`RST`用于复位因某种原因引起出现的错误连接,也用来拒绝非法数据和请求。发送`RST`报文通常意味着发生了某些错误,接收端不必回`ACK`,因此`RST`报文也代表着会话的结束。`NAT`设备收到`RST`报文后也需要等待`2MSL`后释放会话(在`RST`未被对端收到的情况下,对端还有机会通过业务报文再次触发`RST`)。\r\n"},{"id":"Thrift框架","title":"Thrift协议","date":"2023-01-25T00:00:00.000Z","tags":["protocol","network"],"readingTime":12,"slug":"thrift框架","description":"是一种轻量化、语言无关的RPC框架。主要包含三大部分:代码生成、序列化框架、RPC框架。 Thrift网络协议栈 采用的是模型,网络协议栈从下到上分别为:、、、。 Transport 传输层为网络提供了一个简单的抽象,定义了具体的网络传输协议。其本质是一个提供了通过各种协议进行通信的框架,支持包括和...","relativePath":"Tech/Code/rpc/Thrift框架.md","rawContent":"---\r\ntitle: Thrift协议\r\ndate: 2023-01-25\r\ncategories:\r\n- network\r\ntags:\r\n- protocol\r\n- network\r\n---\r\n\r\n`Thrift`是一种轻量化、语言无关的RPC框架。主要包含三大部分:代码生成、序列化框架、RPC框架。\r\n\r\n![](https://github.com/apache/thrift/raw/master/doc/images/thrift-layers.png)\r\n\r\n\r\n\r\n# Thrift网络协议栈\r\n`Thrfit`采用的是`C/S`模型,网络协议栈从下到上分别为:`Transport layer`、`Protocol layer`、`Processor layer`、`Server layer`。\r\n\r\n```\r\n +-------------------------------------------+\r\n | Server |\r\n | (single-threaded, event-driven etc) |\r\n +-------------------------------------------+\r\n | Processor |\r\n | (compiler generated) |\r\n +-------------------------------------------+\r\n | Protocol |\r\n | (JSON, compact etc) |\r\n +-------------------------------------------+\r\n | Transport |\r\n | (raw TCP, HTTP etc) |\r\n +-------------------------------------------+\r\n```\r\n\r\n## Transport\r\n`Thrift` 传输层为网络`write/read`提供了一个简单的抽象,定义了具体的网络传输协议。其本质是一个提供了通过各种协议进行通信的框架,支持包括`HTTP`和`TCP`等。这使`Thrift`能够将底层传输与系统的其余部分解耦(例如,序列化/反序列化)。传输层接口暴露的一些方法包括:`open、close、read、write、flush`等\r\n\r\n传输层分为`TTransport`(客户端)和`TServerTransport`(服务端)两类,配合装饰器模式,通过节点流和包装流的概念来区分各种`Transport`实现。\r\n\r\n```mermaid\r\nclassDiagram\r\nclass Closeable\r\n<> Closeable\r\nclass TTransport {\r\n +open()\r\n +close()\r\n +read()\r\n +write()\r\n +flush()\r\n}\r\n<> TTransport\r\nTTransport..|>Closeable\r\nclass TNonblockingTransport\r\n<> TNonblockingTransport\r\nclass TNonblockingSocket\r\nTTransport<|--TNonblockingTransport\r\nTNonblockingTransport<|--TNonblockingSocket\r\nclass TFileTransport\r\nTTransport<|--TFileTransport\r\nclass TFramedTransport\r\nTTransport<|--TFramedTransport\r\nclass TIOStreamTransport\r\nTTransport<|--TIOStreamTransport\r\nclass TZlibTransport\r\nTIOStreamTransport<|--TZlibTransport\r\nclass TSocket\r\nTIOStreamTransport<|--TSocket\r\nclass TServerTransport {\r\n +open()\r\n +close()\r\n +listen()\r\n +accept()\r\n}\r\n<> TServerTransport\r\nTServerTransport..|>Closeable\r\nclass TNonblockingServerTransport\r\nTServerTransport<|--TNonblockingServerTransport\r\nclass TNonblockingServerSocket\r\nTNonblockingServerTransport<|--TNonblockingServerSocket\r\nclass TServerSocket\r\nTServerTransport<|--TServerSocket\r\n```\r\n\r\n### 客户端传输层(TTransport)\r\n - `TIOStreamTransport`: 是最常用的基于阻塞式`I/O`模型的传输层实现,通过一个输入流和一个输出流实现了所有的传输操作;完美的兼容Java的各种I/O流操作;是`TSocket`和`TZlibTransport`基类。\r\n - `TSocket`: 通过`Socket`实现`TTransport`,阻塞式,侧重建立连接;\r\n - `TZlibTransport`: 基于`InflaterInputStream`和`DeflaterOutputStream(java.util.zip)`实现对输入输出流的压缩,通过调用下一层的`TTransport`实现读写操作,是一个装饰器模式\r\n - `TNonblockingSocket`: 通过`NIO`包中的`SocketChannel`实现非阻塞传输,服务于异步客户端实现\r\n - `TFramedTransport`: 利用`NIO`包的`TByteBuffer`读写缓存,以栈帧为传输单位的`TTransport`装饰器实现;帧头部用4个填充字节来存储数据流长度,保障数据的完整性;\r\n - `TFileTransport`: 用于将数据写入文件,JAVA仅支持read操作,不支持write;\r\n\r\n### 服务端传输层(TServerTransport)\r\n- `TServerSocket`: 服务端阻塞式传输层,基于`ServerSocket`实现;\r\n- `TNonblockingServerSocket`:服务端非阻塞式传输层,基于`NIO`的`ServerSocketChannel`实现;\r\n\r\n## Protocol\r\n`Protocol`定义了一种将内存数据结构映射为有线格式的机制。换句话说,`protocol`定义了指定数据类型如何使用底层`transport`来编解码。因此,`protocol`实现管理编码方案并负责(反)序列化。常用的`protocol`有`JSON、XML、PLAIN TEXT、COMPACT BINARY`等。\r\n- `TBinaryProtocol`: 二进制编码格式的数据传输协议;\r\n- `TCompactProtocol`: 高效率密集的压缩二进制的数据传输协议;\r\n- `TJSONProtocol`: 使用JSON编码格式传输协议,抓包格式可见;\r\n- `TSimpleJSONProtocol`: 简单的JSON格式数据传输协议;仅支持写入,用于脚本语言输出,一般不使用这种\r\n- `TDebugProtocol`: 使用易懂可读的文本格式进行传输,便于debug调试\r\n\r\n`Thrift`中关于`protocol`的接口有:\r\n\r\n```cpp\r\nwriteMessageBegin(name, type, seq)\r\nwriteMessageEnd()\r\nwriteStructBegin(name)\r\nwriteStructEnd()\r\nwriteFieldBegin(name, type, id)\r\nwriteFieldEnd()\r\nwriteFieldStop()\r\nwriteMapBegin(ktype, vtype, size)\r\nwriteMapEnd()\r\nwriteListBegin(etype, size)\r\nwriteListEnd()\r\nwriteSetBegin(etype, size)\r\nwriteSetEnd()\r\nwriteBool(bool)\r\nwriteByte(byte)\r\nwriteI16(i16)\r\nwriteI32(i32)\r\nwriteI64(i64)\r\nwriteDouble(double)\r\nwriteString(string)\r\n\r\nname, type, seq = readMessageBegin()\r\n readMessageEnd()\r\nname = readStructBegin()\r\n readStructEnd()\r\nname, type, id = readFieldBegin()\r\n readFieldEnd()\r\nk, v, size = readMapBegin()\r\n readMapEnd()\r\netype, size = readListBegin()\r\n readListEnd()\r\netype, size = readSetBegin()\r\n readSetEnd()\r\nbool = readBool()\r\nbyte = readByte()\r\ni16 = readI16()\r\ni32 = readI32()\r\ni64 = readI64()\r\ndouble = readDouble()\r\nstring = readString()\r\n```\r\n\r\n## Processor\r\n`Processor`封装了从`input`读取数据和写入数据到`output`的能力。输入和输出流通过`Protocol`对象表示,`Thrift`通过使用`IDL描述文件`来自动生成`Processor`。`Processor`接口非常简单:\r\n\r\n``` java\r\ninterface TProcessor {\r\n bool process(TProtocol in, TProtocol out) throws TException\r\n}\r\n```\r\n\r\n```mermaid\r\nclassDiagram\r\nclass TProcessor {\r\n+process(TProtocol in, TProtocol out)\r\n}\r\n<> TProcessor\r\nclass TBaseProcessor\r\n<> TBaseProcessor\r\nTBaseProcessor..|>TProcessor\r\nclass TMultiplexedProcessor\r\nTMultiplexedProcessor..|>TProcessor\r\nclass Processor\r\nProcessor..|>TProcessor\r\nTBaseProcessor<|--Processor\r\n```\r\n\r\n```java\r\n//只有在定义Server端的时候用到,客户端不需要关心;示例代码: \r\n//PlayerServiceImpl是PlayerService.Iface的实现; \r\nprivate PlayerServiceImpl playerService = new PlayerServiceImpl(); \r\n//Server定义\r\nPlayerService.Processor processor = new PlayerService.Processor<>(playerService);\r\nTServerTransport transport = new TServerSocket(3041); \r\nTServer server = new TSimpleServer(newTServer.Args(transport).processor(processor);\r\nSystem.out.println(\"Starting the simple server...\"); \r\nserver.serve();\r\n```\r\n\r\n### TMultiplexedProcessor\r\n\r\n上述示例中一个Server只绑定一个Service,实际项目中往往有大量的接口服务,如果把所有接口方法写在一个Service接口文件中,不论是代码的结构优化、可读性还是后续维护都带来很多隐患和不便。 幸好,`Thrift`提供了`TMultiplexedProcessor`和`TMultiplexedProtocol`类来帮助开发者拆分和组装业务接口服务。\r\n\r\n服务端使用`TMultiplexedProcessor`类注册多个接口的服务实现类\r\n```java\r\nTMultiplexedProcessor multiplexedProcessor = new TMultiplexedProcessor();\r\nmultiplexedProcessor.registerProcessor(ServiceEnum.PLAYER.serviceName, newPlayerService.Processor(new PlayerServiceImpl())); multiplexedProcessor.registerProcessor(ServiceEnum.GUILD.serviceName, newGuildService.Processor(new GuildServiceImpl()));\r\n```\r\n\r\n客户端使用`TMultiplexedProtocol`类来创建`Client`实例,帮助区分调用具体的服务接口\r\n```java\r\nTProtocol protocol = new TBinaryProtocol(transport);\r\nPlayerService.Client playerService = new PlayerService.Client(new TMultiplexedProtocol(protocol, ServiceEnum.PLAYER.serviceName));\r\nGuildService.Client playerService = newGuildService.Client( new TMultiplexedProtocol(protocol, ServiceEnum.GUILD.serviceName));\r\n```\r\n\r\n## Server\r\n`server`将上面所有的特性整合在一起形成最终的服务:\r\n\r\n- 创建`transport`实例,建立连接通道\r\n- 为`transport`创建输入输出协议`protocol`\r\n- 基于输入输出协议创建`processor`\r\n- 等待新建连接并转交给`processor`\r\n\r\n```mermaid\r\nclassDiagram\r\nclass TServer\r\nclass TSimpleServer\r\nTSimpleServer--|>TServer\r\nclass TThreadPoolServer\r\nTThreadPoolServer--|>TServer\r\nclass AbstractNonblockingServer\r\n<> AbstractNonblockingServer\r\nAbstractNonblockingServer--|>TServer\r\nclass TNonblockingServer\r\nTNonblockingServer--|>AbstractNonblockingServer\r\nclass THsHaServer\r\nTHsHaServer--|>AbstractNonblockingServer\r\nclass TThreadedSelectorServer\r\nTThreadedSelectorServer--|>AbstractNonblockingServer\r\n```\r\n\r\n### TSimpleServer\r\n- 该模式下只有一个工作线程,接受请求和处理数据都在一个线程中\r\n- 简单的阻塞`I/O`模式,一次只能处理一个请求,多个客户端请求被串行处理\r\n\r\n### TThreadPoolServer\r\n- 该模式是对`TSimpleServer`模式的优化,同样是基于阻塞`I/O`实现,每次只能监听一个请求连接\r\n- 采用线程池技术,请求到达后,交给`worker`线程来处理,多线程并行处理了`I/O`的读写。\r\n- 单个请求的处理不会影响服务端整体的性能,但是连接池的规模决定了该模式的工作能力;\r\n\r\n### TNonblockingServer\r\n- 该模式同样只有一个工作线程,相较于`TSimpleServer`模式的不同,该模式采用`I/O`多路复用;\r\n- 基于`I/O`多路复用实现,非阻塞`I/O`,使用`selector`可以同时监听多个到达的连接请求;\r\n- 虽然同时监听多个请求,但是请求的处理还是串行,无法充分利用多核优势,且某个请求处理被阻塞会直接影响后续请求的处理\r\n- 该模式下必须使用`TFramedTransport`\r\n\r\n### THsHaServer\r\n- 该模式可以理解为对`TNonblockingServer`的优化,基于`I/O`多路复用实现,非阻塞`I/O`,采用`Half-sync/Half-async`的工作模式\r\n- `Half-aysnc`是在处理`I/O`事件上 (accept/read/write),`Half-sync`用于`handler` 对`rpc`的同步处理上。\r\n- `THsHaServer` 在完成数据读取之后,将业务处理过程交由一个线程池来完成,主线程直接返回进行下一次循环操作,效率大大提升,但是主线程需要完成所有`socket`的监听以及数据读写,当请求书较大且发送数据量较多,监听的`socket`请求不能被及时处理。\r\n- 该模式必须使用`TFramedTransport`\r\n\r\n### TThreadedSelectorServer\r\n\r\n`THsHaServer`缺点是主线程仍然需要完成所有`socket`的`listen`和`accept`、数据读取和数据写入操作(read/write)。当并发请求数较大时,且发送数据量较多时,负责监听的主线程就只有一个。监听socket上新连接请求不能被及时接受。 `TThreadedSelectorServer`是对`THsHaServer`的一种扩充与完善,它将`selector`中的读写`I/O`事件从主线程中分离出来。交给了多个专门负责读写`I/O`事件的`SelectorThread`,同时引入`worker`工作线程池,负责业务处理。它也是种`Half-Sync/Half-Async`的服务模型。\r\n\r\n- 该模式是目前`Thrift`提供的最高级、最复杂也是最高效的工作模式\r\n- 一个`AcceptThread`线程基于`I/O`多路复用,专门同时监听并处理多个到达的新连接;并把建立的新连接转交给`Selector`线程池来进一步处理\r\n- 一个`Selector`线程池对象,同时维护多个`Selector`来为新连接服务,其他几种非阻塞`I/O`实现的模式都只有一个Selector工作\r\n- 一个负载均衡器`SelectorThreadLoadBalancer`对象,负责决策并转发新连接到具体的某个`Selector`\r\n- 一个`ExecutorService`工作线程池对象,其中的`woker thead`完成每个请求对应的逻辑处理\r\n\r\n# IDL(Interface Description Language)\r\n`Thrift`采用`IDL`(Interface Definition Language)来定义通用的服务接口,然后通过`Thrift`提供的编译器,可以将服务接口编译成不同语言编写的代码,通过这个方式来实现跨语言的功能。\r\n\r\n## 注释\r\nThrift支持`shell`风格、`C/C++/Java`风格的注释。示例:\r\n```cpp\r\n# shell风格注释.\r\n// C++/Java 单行注释\r\n\r\n/*\r\n * 多行注释\r\n * 类似C/C++/Java\r\n */\r\n```\r\n\r\n## Description\r\n\r\nThrift文件的描述跟一般语言一样在文件顶部通过注释来表述服务的内容等信息。\r\n\r\n## Document\r\n`Thrift`文档部分包括`0`个或者多个`Header`,然后包括`0`个或多个`Definition`。\r\n\r\n### HEADER\r\n`HEADER`可以是`Thrift include`、`C++ include`或`namespace`声明。\r\n\r\n#### Thrift header\r\n`Thrift include`可以将所有声明包含的`Thrift文档`都包含到当前`Thrift`中。在真正的业务开发中,我们不可能把所有的服务都定义到一个文件中,通常会根据业务模块进行拆分,然后将这些服务`include`到一个入口文件中,然后在最终服务发布上线的时候,`thrift`编译器只需要编译入口文件,就能将所有引入的文件都生成对应的代码。\r\n\r\n```idl\r\ninlucde \"module_a.thrift\" \r\ninclude \"module_b.thrift\"\r\n```\r\n\r\n#### C++ inlcude\r\n用来为当前的`thrift`文件生成的代码中添加一个自定义的`C++` 引入声明\r\n```idl\r\ncpp_include \"string\"\r\n```\r\n\r\n#### Namespace\r\n\r\n语法:`namespace NamespaceScope 标识符`\r\n `Namespace` 用来声明使用哪种语言来处理当前 `thrift`文件中定义的各种类型,`NamespaceScope`就是各种语言的标识,也可以指定为通配符`*`标识,标识`thrift`文件中的定义适用于所有的语言。除此之外 Namespace 还有一个作用就是避免不同`Identifier`定义的命名冲突。如下示例中`Namespace`声明语句,标识当前的`thrift`文件适用于`java`,`NamespaceScope`后面紧跟着的`Identifier`在不同语言中会有不一样的表现。\r\n \r\n```idl\r\nnamespace java cc.shinerio.test\r\n```\r\n\r\n### Definition\r\n\r\n`thrift`支持的数据类型包括基本类型、结构体类型、容器类型、异常类型和服务类型。\r\n\r\n#### 基本类型(Special Types)\r\n\r\n- bool: 布尔值\r\n- byte: 8位有符号整数\r\n- i16: 16位有符号整数\r\n- i32: 32位有符号整数\r\n- i64: 64位有符号整数\r\n- double: 64位浮点数\r\n- string: UTF-8编码的字符串\r\n- binary: 二进制串\r\n\r\n```idl\r\n# int 类型常量\r\ni8 count = 100 \r\n# doubule 类型 \r\ndouble money = '13.14'  \r\n# 使用科学计数法表示0.000012\r\ndouble rate = 1.2e-5  \r\n# 表示 350000000\r\ndouble salary = 3.5e8  \r\nstring testStr = 'hello thrift'\r\n```\r\n\r\n#### Const\r\n\r\nConst用来声明一个常量,语法为:\r\n```idl\r\nconst FieldType Identifier = ConstValue [ListSeparator]\r\n```\r\n\r\nListSeparator`这个分隔符就好比`Java`中一句话结束后的`;`,在`IDL`中分隔符可以是`,`或者`;`。大部分情况下可以忽略不写。\r\n\r\n```idl\r\nconst i8 count = 100  \r\nconst string strConst = 'hello thrift'\r\nconst list names = [ 'tom', 'jack', 'peter' ]\r\n```\r\n\r\n#### 容器类型(Containers)\r\n\r\n- list: 有序元素列表\r\n- set: 无序无重复元素集合\r\n- map: 有序的key/value集合\r\n\r\n```idl\r\nstruct Grade { \r\n1: i32 id, \r\n2: string name, \r\n3: list%3Ci32%3E scores\r\n}\r\n```\r\n\r\n#### Typedef\r\n`Thrift`支持`C/C++`风格的自定义类型,语法`typedef 原类型 自定义类型`\r\n\r\n```idl\r\ntypedef i32 integer32 \r\n```\r\n\r\n#### Enum\r\n`Thrift`枚举类型的值只支持`int32`,第一个元素如果没有给值那么默认是`0`,之后的元素如果没有给值,则是在前一个元素基础上加`1`,语法:\r\n\r\n```idl\r\nenum identifier { \r\n\tkey1 = value1,\r\n\tkey2 = value2,\r\n}\r\n```\r\n\r\n如:\r\n```rust\r\nenum Season\r\n{\r\n SPRINT = 1,\r\n SUMMER, //值是2\r\n AUTUMN, //值是3\r\n WINTER = 5\r\n}\r\n```\r\n\r\n#### Struct\r\n`thrift`定义中一个`struct`等价于`OOP`编程中的`class`,不具备继承能力,但是可以嵌套。`thrift`结构体中的变量还可以存在默认值,直接写在`IDL`文件中。`struct`的每个元素包括一个唯一的数字标识、一个数据类型、一个名称和一个可选的默认值。语法如下:\r\n\r\n```idl\r\nstruct identifier { \r\nField ID: Field Req? type identifier (= value)? \r\n}\r\n```\r\n\r\n`Filed Req`是一个可选值,包括required和optional,如果两者都没声明,则是requiredness,三者作用如下:\r\n\r\n1. `required`代表字段在输入和输出的时候必选,`required`字段一般不能随着版本删除,否则会带来不兼容问题\r\n2. `optional`代表字段是可选的,当字段有值时会被序列化和序列化,否则是空值。\r\n3. `requiredness`是一种中间状态,一般代表这个数据输出是必选的,输入是可选的,这是一种非常好的兼容方式。\r\n\r\n示例:\r\n```idl\r\nstruct UserProfile { \r\n1: i32 uid = 1, \r\n2: string name = \"User1\", \r\n3: required string password,\r\n4: optional string metadata\r\n}\r\n```\r\n\r\n#### Union\r\n类似于`C++`的`union`,成员默认全部是`optional`类型,只会传输多个字段中的一个。\r\n```idl\r\nunion UserInfo {\r\n1: string phone, \r\n2: string email\r\n}\r\n```\r\n\r\n#### 异常类型(Exceptions)\r\n异常在功能上等同于`Structs`,不同之处在于其会在每种目标编程语言中选择合适的异常基类继承,以便与任何给定语言中的本地异常处理无缝集成。\r\n\r\n```idl\r\nexception MyExcepption {\r\n 1: i32 errorCode,\r\n 2: string msg\r\n}\r\n```\r\n\r\n#### 服务类型\r\n\r\n服务由一组命名函数组成,每个函数包含一个参数列表和一个返回值类型。 服务的定义在语义上等同于在`OOP`中定义接口(或`abstract`类)。`Thrift`编译器生成实现该接口所有函数的客户端和服务器函数块。\r\n\r\n请注意,除了所有其他已定义的`Thrift` 类型之外,`void`也是函数返回的有效类型。 此外,可以将`oneway` 修饰符关键字添加到`void` 函数,此时生成的代码将不等待响应。显然这种方式可以提升客户端的性能,适用于客户端并不关键服务端执行结果的情况。但是请注意,`void` 返回值的意义在于服务端将向客户端返回一个响应,以保证操作已在服务器端完成。 使用`oneway`调用,客户端只能保证请求在传输层传输成功。 服务器可能会并行或乱序执行同一客户端的`oneway`方法调用。\r\n\r\n```idl\r\nservice MyService {\r\n void test1();\r\n oneway void test2();\r\n}\r\n```\r\n\r\n"},{"id":"shell编程","title":"shell编程","date":"2023-01-24T00:00:00.000Z","tags":["linux"],"readingTime":12,"slug":"shell编程","description":"本文主要介绍shell编程的基本语法以及实际应用中的常见命令。 1. 注释 1.1. 单行注释 1.2. 多行注释 多行注释也可替换成或 2. 变量 2.1. 变量定义 使用的形式,VALUE如果是字符串的话,可以使用单引号、双引号或者不加引号。单引号内的任何字符都会原样输出,不能进行转义,单引号内...","relativePath":"Tech/Code/shell编程.md","rawContent":"---\r\ntitle: shell编程\r\ndate: 2023-01-24\r\ncategories:\r\n- linux\r\ntags:\r\n- linux\r\n---\r\n本文主要介绍shell编程的基本语法以及实际应用中的常见命令。\r\n# 1. 注释\r\n## 1.1. 单行注释\r\n```shell\r\n# 这是单行注释\r\n```\r\n## 1.2. 多行注释\r\n```shell\r\n:< 文章列表 - Shinerio's Blog 符号,变量加`{}`是可选的,方便解释器识别边界。将变量加上`{}`是一个好习惯,不容易出错。\r\n```shell\r\nyour_age=25\r\nyour_name=\"shinerio\"\r\nyour_name=shinerio\r\nyour_name='shinerio'\r\necho $your_age # 输出25\r\necho $your_name # 输出shinerio\r\necho \"${your_name}hello\" #这里{}必须添加,否则解释器认为是${your_namehello}\r\nstr='hello'' # 非法\r\nstr='hello'__'world' # 成对出现的单引号做字符拼接\r\necho $str #输出hello__world\r\nstr1='helloworld${your_age}\\nnew line'\r\nstr2=\"helloworld${your_age}\\nnew line\"\r\necho $str1 # 输出helloworld${your_age}\\nnew line\r\necho $str2 # 输出helloworld25\\nnew line\r\necho -e $str2 # -e开启转义 \\n表示换行 echo默认换行,通过\\c强制不换行\r\n#helloworld25\r\n#new line\r\n```\r\n## 2.3. 只读变量\r\n使用`readonly VARIABLENAME`设置变量只读,只读变量的值不能更改\r\n```shell\r\nreadonly name='jack'\r\nname='kelly'\r\nzsh: read-only variable: name\r\n```\r\n## 2.4. 删除变量\r\n使用`unset VARIABLENAME`删除变量。\r\n# 3. 字符串操作\r\n## 3.1. 字符串拼接\r\n```shell\r\nstr1=\"hello\"${your_name}\"world\"\r\nstr2=\"hello${your_name}world\"\r\nstr3='hello'${your_name}'world'\r\n```\r\n## 3.2. 字符串长度\r\n```shell\r\nstr='hello'\r\necho ${#str}\r\n5\r\n```\r\n## 3.3. 字符串截取\r\n`${string:start:length}`,索引从0开始,从start(包含)开始截取共计length长度的字符。\r\n```shell\r\nstr='helloworld'\r\necho ${str:0:5}\r\nhello\r\n```\r\n## 3.4. 字符串运算符\r\n\r\n| 运算符 | 说明 | 举例 |\r\n| ------ | --------------------------------------- | ----------------------- |\r\n| `=` | 检测两个字符串是否相等,相等返回true | `[ 'hello' = 'hello' ]` |\r\n| `!=` | 检测两个字符串是否相等,不相等返回true | `[ 'hello' != 'ello' ]` |\r\n| `-z` | 检测字符串长度是否为0,为0返回true | `[ -z '' ]` | \r\n| `-n ` | 检测字符串长度是否不为0,不为0返回true | `[ -n 'hello' ]` |\r\n| ` 文章列表 - Shinerio's Blog | 检测字符串是否为空,不为空返回true | `[

Github Oauth

▐▛███▜▌ Claude Code v2.1.34 ▝▜█████▛▘ Opus 4.6 · Claude API ▘▘ ▝▝ C:\workspace\code\shinerio.github.io > 1. 文章详情页,点击划词后,点击使用github登录,页面直接404,需要修复 2. 底...

BGP Speaker

在传统的网络设备中,我们通常直觉地认为“一台设备 = 一个 BGP 进程 = 一个 Speaker”。但在现代网络设计和高级设备架构中:一台物理路由器可以拥有多个 BGP Speaker。 为了理解这一点,我们需要区分“物理实体”和“逻辑协议实例”。以下是几种实现“多 Speaker”的典型场景: ...

BGP路由器分类

在BGP(边界网关协议)中,路由器的分类通常根据其在自治系统(AS)中的位置以及建立邻居关系的方式来划分。 1. 按协议划分 1.1. EBGP 路由器 (External BGP) 当两台运行 BGP 的路由器处于不同的自治系统(AS)时,它们之间建立的邻居关系称为 EBGP。 - 位置: 通常位...

Awesome Tools

1. 全平台 - freefilesync,文件夹同步工具 - ChatGPT流媒体解锁检测脚本: - https://gitlab.com/fscarmen/warp解锁应用端chatgpt 2. macos 2.1. 媒体工具 - snipaste截图软件 - kap屏幕gif录制工具,对于博客...

Claude Code

1. claude code配置 1.1. ide集成 在vscode中安装claude code插件,然后在claude code命令行界面使用即可连接到vscode。claude code就可以和vscode进行交付,感知你在vscode中选中的代码、文件,claude code的修改也会在vs...

Subagents

1. 子代理是专门处理特定类型任务的 AI 助手,当Claude遇到与子代理描述相匹配的任务时,它会将任务委派给该子代理,由其独立工作并返回结果。 - 每个子代理都在自己的上下文窗口中运行 - 拥有自定义的系统提示词(System Prompt) - 特定的工具访问权限和独立的权限设置。 优势: -...

Cloudflare免费Worker

cloudflare一段时间之前推出的一项免费服务, 允许在CDN服务器上运行js脚本或wasm 截止到这篇文章写完的时候,这仍是一项长期免费服务,免费套餐为每天 100000 个请求, 大概是100个人 每人请求100次, 或0.01个人 每人请求10000000次

Quantumult X

1. 参考配置 https://raw.githubusercontent.com/limbopro/Profiles4limbo/main/full.conf 2. 规则仓库 https://github.com/blackmatrix7/iosrulescript/tree/master 3. ...

Cloud Wan竞品分析

1. 腾讯 - CCN路由同步,默认会同步所有路由表 - - VPC多条路由冲突的时候,可以支持启用、停用 1.1. 跨地域流量管理 - 单向的流量调度规则限速带宽总和不得超过带宽上限。 - 带宽上限提高后,默认规则的限速带宽不会自动调整,需手动调整 2. 阿里 - CEN路由同步,默认只会同步默认...

IBGP与EBGP区别

1. 使用场景 eBGP主要用于: - 不同运营商/组织之间交换路由,例如中国电信与中国联通的互联互通 - 企业多归属(multihoming)接入多个 ISP - IXP(互联网交换点)中各参与方之间的路由交换 iBGP 主要用于: - 在一个大型 AS 内部传递从 eBGP 学到的外部路由信息,...

BGP协议优先级

1. 核心逻辑 1.1. “大”即是好的(高优先级) Weight 和 Local Preference:这两个属性是管理员手动干预的首选。数值设置得越大,代表你越“偏好”这条路径。 1.2. “短/小”即是好的(低开销) - AS-Path 长度:这体现了 BGP 的路径矢量特性,跳数越少代表路径...

Openspec

A "change" in 是一个“承载着围绕一项工作所进行的所有思考和规划的“集合。文件夹位于,包含proposal, specs, design, tasks。 工作流程 目录结构 - - 这是最重要的目录,存储了系统当前是如何运行的完整描述。 - 按domain组织:为了防止单个文档过大,它按...

Claudemd

| 类型 | 位置 | 范围 | 版本控制 | 典型用途 | | ------ | ----------------------- | ---- | ----- | ------ | | 全局 | | 所有项目 | 不共享 | 个人编码偏好 | | 项目 | | 当前项目 | 共享给团队 | 团队规...

SDD(Spec Driven Develop)

Kiro的spec流程被设计为三个步骤:需求 (requirements.md) → 设计 (design.md) → 任务 (tasks.md)。每个工作流步骤都由一个Markdown文档表示,Kiro会引导你达成这三个步骤。 1. 需求文档 它被构建为一个需求列表 1. 每个需求代表一个“用户故...

Run Code

runcode是一个来自veadk库的内置工具,它提供了一个安全的代码执行沙箱功能。这个工具允许AI Agent运行用户请求的代码片段(主要是 Python3),并返回执行结果。该工具通过字节跳动云服务的API在远程安全环境中执行代码,并具有会话管理和身份验证功能。 1. 工作原理 1.1. 工具注...

Demo

1. summary 2. 3. 对话前,无任何相关记忆 通过对话告知喜欢滑雪 再次查询记忆库 4. 5. 部署MCP服务的时候可以选择API KEY自动生成MCP的入站身份鉴权 agentkit一直无法拉起mcp工具集,这里使用claude code本地测试 6. 环境变量参考 7. 相关链接 -...

Awscli

install 配置账号 1. ref https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

Invoke

调用需要具备以下权限

Introduce

Amazon Bedrock AgentCore 同时具备框架无关性和模型无关性,让你能够灵活、安全且大规模地部署和运行高级 AI 智能体。无论你是使用 Strands Agents、CrewAI、LangGraph、LlamaIndex 或任何其他框架构建智能体,也无论你在哪个大语言模型(LLM)...

Gateway

1. v2n agent runtime需要的short-term memory需要通过v2n连接到用户vpc中的postgresql(内存中易失)

AgentCore

Amazon Bedrock AgentCore是用于构建、部署、高效运行Agent的最先进的智能化平台,其服务包括Runtime, Gateway, Memory, Identity, Observability, Browser, Code Interpreter, Evaluation, po...

Oauth2.0

OAuth 2.0是一个关于授权(Authorization)的开放标准。它允许用户让第三方应用访问该用户在特定服务上存储的私有资源(如照片、联系人等),而无需将用户名和密码提供给第三方应用。最常见的例子就是:你使用“微信登录”或“GitHub登录”来注册一个新的网站,而不需要在该网站重新设置密码。...

记忆

1. 短期记忆 LLM的核心架构在推理时,每一轮都是独立的计算过程。因此如果不把所有对话历史都带上的话,LLM就只能针对当前问题就会回答,无法感知历史对话。 在AI中,短期记忆通常指“对话上下文”,包括: - prompt - 对话历史 - 前序大模型推理结果 - 前序工具执行结果 短期记忆有两种存...

开源模型汇总

1. | Model | Total Params | Activated Params | Context Length | | :--------------: | :---------------: | :-------------------: | :----------------: | ...

Uv

1. 设置pip源 配置环境变量 2. 设置cache位置 3. 初始化项目并指定最低python版本 4. 全局安装tool 如果安装Python包是为了在终端任何地方运行它的命令(比如 , , 或者你提到的 ),可以使用 : - 效果: 它会为这个工具创建一个隐藏的独立环境,但把它的可执行命令软...

MCP Gateway

1. ref https://www.volcengine.com/docs/86681/1844858?lang=zh

Agentkit

1. 支持协议: AgentKit智能体运行时支持A2A、MCP、标准HTTP三种通信协议 2. agent访问方式 - 公网访问:默认访问方式。 - 私网访问:选择同地域中的任意一个VPC和子网,每个可用区支持最多选择一个子网。 3. 大模型 通过api endpoint对接的是火山方案 3.1....

Huoshan

基本概念

1. 参数大小 | 缩写 | 英文全称 | 中文含义 | 数值(科学计数法) | 对应中文单位 | | ------ | ------------ | -------- | ------------- | ----------- | | M | Million | 百万 | $10^6$ | 100...

Bedrock

1. Model catalog - Amazon Bedrock Foundation Models - Amazon Bedrock Marketplace - Bedrock Custom Model Import 1.1. 区别对比 | 特性 | Foundation Models | Ma...

Awesome Tools

:pdf论文翻译,提供中英文对照。

Claude Desktop通信过程

MCP

MCP与AI Agent

AI Agent向LLM “提供” 了它能使用的工具列表,这个过程通常通过以下两种方式实现: 1. 静态工具注册(Static Tool Registration): - 在设计 AI Agent 系统时,开发者会预先定义并注册一系列可供 Agent 调用的工具。 - 每个工具都有一个清晰的 描述 ...

MCP开发原理

1. MCP Server开发 1.1. 注意事项 - 使用stdio作为transport layer的时候,不要进行任何控制台输出 1.2. 交互过程 INITIALIZE = "initialize" - 协商协议版本 - primitives支持情况,如tools、resources、pro...

MCP概念理解

1. 基本概念 MCP(Model Context Protocol 模型上下文协议) 是一个开放的标准化协议,用于在AI模型和外部数据源、工具之间建立安全、可控的连接。它定义了AI系统如何访问和利用外部上下文信息的规范。MCP就像是AI应用程序的USB-C接口,为AI模型提供了一种标准化的方式来连...

MCP配置

1. 基本定义 - 每个MCP服务器都是一个独立json对象,以服务器名称作为key - key在MCP配置文件中以及全局配置文件和项目配置文件中必须是唯一的 - 每个MCP服务器条目对象都必须具有属性 2. local mcp server - (可选):在执行之前,先将进程的工作目录切换到指定路...

开发与测试

1. tools 1.1. stdio 启动命令 inspector调试 mcp config 1.2. SSE 启动命令 inspector调试 mcp config 1.3. streamable http mcp-server仅需修改transport即可 mcp confi需要将修改为,例如...

Prompt

概念

1. Message类型 System message 在大模型内部是每次加在了用户输入的前面。在 的大模型设计的时候,有三种不同的message 类型,这三者是有明显区别的。 - System Message:对大模型的角色进行定义,并输入一些基础的指令,包括大模型的身份、一些用于提高安全性的指令...

RAG

RAG(Retrieval-Augmented-Generation, 检索增强生成)is a process that helps AI models "look things up" before they answer, like accessing my calender or the we...

Vector Database

Embeddings用数值形式的向量,在高维空间表示数据(通常是文本等非结构化的数据)。传统的关系型数据库并不适合存储和搜索这些向量表示。 向量存储库能够使用相似度算法对相似向量进行索引和快速搜索,使得应用程序能够在给定目标向量的情况下找到相关向量。 例如,在个性化聊天机器人的案例中,用户会向生成式...

AI

Agntcy

1. OASF Open Agentic Schema Framework一个基于OCI(Open Container Initiative)的可扩展数据模型,用于描述agent的属性并确保agent的唯一标识。OASF支持描述A2A代理和MCP服务器,并且可以扩展以支持其他常用格式,例如Copil...

Claude Skills

1. 原理 claude skill是一类模块化能力组件,用于拓展 Claude的功能边界。每项技能都封装了: 1. 元数据 2. 指令说明 3. 可选配套资源(脚本、模板) Claude Agent Skills的设计哲学在于模块化与按需加载,旨在解决传统代理系统中常见的上下文冗余、性能衰减以及操...

CrewAI

1. install https://docs.crewai.com/en/installation

执行计划

1. explain和explain anayze 和生成的执行计划通常是一致的,但并不能保证完全一致。 1.1. 核心功能对比 | 对比维度 | EXPLAIN | EXPLAIN ANALYZE | | -------- | ------------- | --------------- | |...

Excalidraw使用示例

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements %%>>>text element-link:Excalidraw<<<%%shinerio's blog ^0js7...

锥型NAT和对称型NAT

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== %% Drawing %%

Mysql可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

Mysql当前读下幻读问题

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Excalidraw Data Text Elements 事务A ^skXrctjq 事务B ^eJoYKov2 insert into ord...

Mysql当前读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^yULSUGNg 事务B ^RjMMNDeM select from orders where 10 sel...

Mysql快照读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^b7TiLH2p 事务B ^bMLs1cuE select from orders; ^nEabCTcv i...

Postgresql不可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

A2A

协议是一项开放标准,旨在解决人工智能快速发展领域中的一个基本挑战:由不同团队构建、使用不同技术且归不同组织所有的AI Agents,如何有效通信与协作? 随着人工智能代理变得更加专业化和强大,它们在复杂任务上协同工作的需求也日益增长。想象一下,用户让其主要的人工智能助手规划一次国际旅行。这一个请求可...

AI Agent

AI Agent综述(Chatbot + Workflow + AI Agent + Function Call + MCP)

1. LLM E.G. Chat GPT, Google Gemini and claude. 1.1. Work Mechanism Human provide an input and the LLM responds with an output. > input(prompt) -> LLM...

设计模型(Work Flow & Agent)

1. 工作流模式(Predefined Workflow) 1.1. 流水线 流水线是最简单直接的工作流编排,通过编排一个顺序处理的流程,让模型逐步执行和推理。 场景示例: 文档处理系统:文档上传 → 格式转换 → 内容提取 → 语义分析 → 结果存储 1.2. 路由分发 路由分发是将输入分类(LL...

AI Code

AI Platform

Aws

Dify

1. chatbot - 核心就是对话 - 支持自定义System prompt - System prompt和Conversation opener支持jianjia变量渲染 - 支持rag 输入变量后,进行对话 2. Agent Agent相比chatbot增加了工具调用的能力 2.1. Co...

字典树(Trie)

1. 单级字典树 字典树最基础的应用——查找一个字符串是否在「字典」中出现过,也可以用来做最长前缀匹配。 如下图,每个路径代表一个字母,每个节点存储以该节点结尾的字符串是否存在,构建如下的一个字典树。 1-4-8-12,且12节点记录值为true,则代表存在这样的路径的字符串,即存在caa字符串。 ...

时间轮

时间轮(Timing Wheel)是George Varghese和Tony Lauck在1996年的论文实现的,是一种实现延迟功能(定时器)的精妙的高级算法,其算法应用范围非常广泛它在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一,在Java开发过程中常用的Dubbo、Net...

Dapr(Distributed Application Runtime)

dapr重点落在了runtime上,runtime是一个抽象概念,提供了运行时的实现,不需要开发人员操心,比如Java的runtime环境就是jvm。核心思想是模块化,通过sidecar的方式实现,然后通过本地rpc或者http调用。 1. Multi Runtime 分布式应用的需求: - 生命周...

Cloud Network

EIP分类

1. 全动态BGP(多线EIP) 云服务提供商的公网IP地址通过BGP与多个运营线直连的链路播报给多个运营商。BGP类型的带宽具备动态路由收敛能力,可靠性和抗DDos能力好,但价格相对静态BGP来说较贵。云服务提供商可以根据设定的寻路协议实时自动优化网络结构,以保持客户使用的网络持续稳定、高效。 2...

ER的作用

使用peering等方式构建的网络结构是Full Mesh,而企业路由器的网络结构是中心辐射型(星型拓扑)。

Aliyun LB介绍

1. ALB - 每个可用区占用三个ip,一个vip,两个local ip - 每个vip可以独立绑定eip - 。ALB实例的杭州可用区H发生故障时,ALB能够在短时间内停用该可用区,并继续使用其他启用的可用区提供服务。 - ALB默认开启跨AZ负载均衡,即ALB在同地域跨可用区的后端服务之间分配...

AWS LB介绍

应用程序负载均衡器(ALB)、网络负载均衡器(NLB)和网关负载均衡器(GLB)是云中使用的三类负载均衡器。 为了重定向应用程序流量 - ALB 会检查请求的内容,例如 HTTP 标头或 SSL 会话 ID - NLB 会检查 IP 地址和其他网络信息,以最佳方式重定向流量 - GLB 充当透明的网...

HW LB介绍

1. LB实例 - 可用区:选择部署的可用区列表,可用区越多,价格越贵 - 应用型和网络型:可组合选择购买应用型和网络型,组合不同,收费不同 - 网络配置: - 所属VPC - 网络类型:ipv4、ipv6,可组合选择,或都不选(只提供公网接入);选择ipv4后,可提供私网IP接入的私网负载均衡;选...

Nginx使用

1. nginx常用命令 Nginx的命令在控制台中输入就可以看到完整的命令,这里列举几个常用的命令: - nginx -s reload 向主进程发送信号,重新加载配置文件,热重启 - nginx -s reopen 重启 Nginx - nginx -s stop 快速关闭 - nginx -s...

Nginx架构与原理

1nginx默认的启动方式是多进程的方式,nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。worker的数量可以通过配置文件中的配置项控制,一般建议设置与机器cpu核数相当。 Nginx采用了事件驱动的模型,期望worke...

Aws

aws的nat网关作为vpc的一个特性,创NAT网关的时候可以选择公网还是私网。流量经过NAT网关,源地址都会转成网关的私网地址,公网NAT后面会经过internate gateway,然后将私网地址转换成公网地址。 1. 公网NAT 1.1. 性能 - 默认5 Gbps,最高100 Gbps - ...

AZURE

1. 公网NAT 只支持SNAT,不支持DNAT 1.1. 性能 - 50 Gbps - 100W - 5M PPS 1.2. other 1.2.1. 支持自定义TCP超时时间 1.2.2. 支持一组地址连续的公网地址池 支持16个EIP或/28大小的公共EIP前缀 1.2.3. 分布式VM提供服...

GCP

google的NAT网关比较特别,不是一个独立的网关设备,而是做在源计算节点上的源端NAT。Cloud NAT 是一种没有代管式中间代理的软件定义解决方案,采用,具有高可靠性、高性能和高可伸缩性。 1. 公网NAT Public NAT 网关会为使用网关创建与互联网的出站连接的每个虚拟机分配一组外部...

华为云

1. 竞争优势 不需要使用peering/er等实现跨vpc访问的能力,私网nat天然支持跨vpc网络访问。

腾讯云

1. 公网NAT 1.1. 性能 - 默认限速5 Gbps,最大可支持50 Gbps - 并发连接200万 - 10万新建 - 不占用vpc用户的私网ip 1.2. 配额 1.2.1. 网关 - 一个VPC支持3个NAT网关 - 每个NAT网关绑定最多10个EIP 1.2.2. SNAT - 单网关...

阿里云

1. 公网NAT 2. 特性 - 支持一键全通 - snat只允许修改eip - - 支持按量计费和带宽包 2.1.1. NIS(Network Intelligence Service) 支持实例级别的一键诊断,可以检测实例的配置与运行状态,并能根据诊断的异常项提供智能修复建议。诊断内容主要包括:...

Aws HyperPlane

- top完成所有包转发和包处理,一旦网络连接建立了,转发只在Top层完成 - Flow Master记录网络连接,充当Decider的缓存 - Decider实现网络逻辑,对于NAT来说就是进行会话分配 Flow Master可以主主扩展,利用率可以做得比较高,而Decider利用率相对来说较低。

Dpdk

Ovs

现在OpenVSwitch主要由三个部分组成: - ovsdb-server:OpenFlow本身被设计成网络数据包的一种处理流程,它没有考虑软件交换机的配置,例如配置QoS,关联SDN控制器等。ovsdb-server是OpenVSwitch对于OpenFlow实现的补充,它作为OpenVSwit...

发展历史

1. 转发平台发展 - 以openvswitch为代表的第一代内核态软件转发平台 - 以dpdk为代表的第二代内核态软件转发平台 - 以P4或NP为代表的第三代硬件转发平台。 第一代发展到第二代解决了内核态和用户态上下文切换代价较高的问题; 第二代发展到第三代解决了软件转发平台性能不足的问题。 第一...

云网络诞生

1. 产业竞争核心 - 芯片 - 操作系统 - 应用 2. 转发能力发展路线 虚拟交换机发展从10Gbit/s(以原始openvswitch为代表的内核态转发,使用 kernel 作为 datapath) -> 25Gbit/s(以dpdk技术为依托的内核态转发,使用用户空间作为 datapath)...

1. Vscode配置C++开发环境

1. 插件安装 C/C++ 2. 安装gcc 3. 配置C++环境 C++环境需要.vscode 文件夹下的以下三个文件共同定义 - ccppproperties.json :对C/C++扩展的设置。 - tasks.json :定义如何生成可执行文件。 - launch.json :定义如何调试可...

2. 基本语法

1. 程序入口 在 C++ 标准中, 函数是程序启动后调用的第一个用户定义函数,它有两种标准的定义形式: 1. :这种形式表示 函数不接受任何命令行参数。它向调用者返回一个整数值,用于表示程序的退出状态。通常,返回值 表示程序正常结束,非零值表示程序出现异常或错误。 2. :这种形式允许程序接收命令...

3. 命名空间

命名空间(namespace)是 C++ 中一项重要的特性,它可以将全局作用域划分为不同的部分,从而避免不同库或者不同模块之间的命名冲突。标准 C++ 库中的所有标识符(像类、函数、对象等)都被定义在了 命名空间里。 1. using namespace std 在没有使用 时,若要使用标准库中的标...

4. 变量定义与内存空间分配

C++ 中定义变量时,即使不进行初始化,内存空间通常也会被分配,不过不同类型的变量在分配内存空间的时机和默认初始值方面存在差异。 1. 局部变量(自动存储期) 局部变量一般定义在函数内部或者代码块内部,具有自动存储期。当程序执行到定义局部变量的语句时,会立即为其分配内存空间,但不会自动初始化,该内存...

5. String字符串

c++包含两种风格的字符串 - C 风格字符串 - C++ 引入的 string 类类型 1. char类型 C风格的字符串起源于C语言,并在C++中继续得到支持。字符串实际上是使用null字符\0终止的一维字符数组。因此,一个以null结尾的字符串,包含了组成字符串的字符。 2. 字符串函数 | ...

Code

Go Env

1. GOROOT与GOPATH - 环境变量表示 Go 语言的安装目录。在中,的默认值是,而在或中的默认值是,如果将Go安装在其他目录中,而需要将GOROOT的值修改为对应的目录。另外,则包含Go为我们提供的工具链,因此,应该将配置到环境变量 PATH 中,方便我们在全局中使用 Go 工具链。 -...

Go Mod

从 Go 1.11开始,Go引入了Go Modules作为官方的依赖管理解决方案。在Go Modules模式下,依赖项会被下载到目录下,命令默认会将可执行文件安装到目录下。文件位于项目根目录下,用于记录项目的模块信息和依赖项及其版本,一个典型的例子如下: 1. go mod命令 2. module ...

Go

Go命令行参数

- os.Args变量是一个字符串(string)的切片(slice) - os.Args的第一个元素,os.Args[0], 是命令本身的名字;其它的元素则是程序启动时传给它的参数

Channel

1. 声明和初始化 1.1. 仅声明(未初始化) 声明一个 int 类型的 channel,但未初始chnilpanicmake()make()ch := make(chan int)chch := make(chan int, 3)3varvar ch chan int = make(chan i...

Context

go1.7加入了一个新的标准库context,用来简化对于处理单个请求的多个goroutine之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个API调用。 fff88f">当一个上下文被取消时,它派生的所有上下文也被取消。当context生命周期结束时,可以从管道接收消息,子协...

Defer

1. 关键字 defer 用于注册延迟调用。 2. 这些调用直到return前才被执。因此,可以用来做资源清理。 3. 多个defer语句,按先进后出的方式执行。 4. defer语句中的变量,在defer声明时就决定了,语句中的fff88f">函数调用参数会在语句执行时立即求值,并保存下来。这意味...

Goto与Label

Go语言也支持label(标签)语法:分别是和 、。 - break label可以跳出label层级循环 - continue label可以从label继续下一次循环 - goto可以无条件的跳转执行的位置,但是不能跨函数,需要配合标签使用 执行结果 输出结果

函数

1. 结构体返回值 在Go语言中,函数返回结构体时,接收它的变量类型可以是值类型或指针类型 1.1. 使用值类型接收值类型返回值 - 结构体较小(如 2-3 个字段的结构体),直接复制不会有明显性能影响。 - 结构体的数据不需要在外部修改,返回值是独立的副本。 输出如下 1.2. 使用指针类型接收指...

变量赋值&结构访问

1. 取地址与解引用 是取地址符号,放到一个变量前使用就会返回相应变量的内存地址。如 是指针运算符,一个指针变量指向了一个值的内存地址。放到一个变量前可以对指针解引用,获取指针指向的实际变量。 输出如下 2. 变量赋值 go中直接对结构体进行复制,会进行值拷贝。 结构体的指针属性也会拷贝,但指针指向...

基本语法

基础

1. 变量初始化 go中使用var声明的变量会自动初始化,如下是等价的 2. array 1. 数组:是同一种数据类型的固定长度的序列。 2. 数组定义:,比如:,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。 3. 长度是数组类型的一部分,因此,和是不同的类型。 4. 数组可以通过...

并发

异常处理

在Go语言中,和都是用来处理错误情况的,但是它们的使用场景和应用场合是不同的。 1. panic 更适用于程序出现不可恢复的错误情况,例如违反了一些重要的前提条件、发生了一些严重错误等。一般来说,如果程序出现了,就意味着程序已经处于一个不可控的状态,无法继续执行下去。会导致程序立即终止并打印出错误堆...

循环与Select

1. select 语句会从上到下依次检查每个 的通信操作语句,每个case必须是一个通信操作,要么接收,要么发送。 - 如果发现某个 的通信操作可以立即执行,就会执行该 语句块并跳出 代码块。 - 如果多个 均可执行,则会随机选择一个执行。 - 如果没有任何一个 可以执行,则会执行 语句块(如果存...

类型断言

类型断言提供了访问接口值底层具体值的方式。 该语句断言接口值 保存了具体类型 ,并将其底层类型为 的值赋予变量 。若 并未保存 类型的值,该语句就会触发一个 panic。 为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。 t, ok := ...

结构体

常用Tool

1. golps golps 是LSP(Language Server Protocol)的一个语言端(Server)实现,是针对 Go 语言的LSP实现。定义了在编辑器或IDE中与语言服务器之间使用的协议,该语言服务器提供诸如自动完成,转到定义,查找所有引用等语言功能。语言服务器索引格式(LSIF...

Kafka

Redis

1. simple example 2. redis pool

Web

测试

Golang单元测试对文件名和方法名,参数都有很严格的要求。 1. 文件名必须以xxtest.go命名 2. 方法参数必须 3. 使用go test执行单元测试 在文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。 | 类型 | 格式 | 作用 | | ---- | ----------...

Benchmark

Go语言标准库内置的testing测试框架提供了基准测试(benchmark)的能力,能让我们很容易地对某一段代码进行性能测试。 本文中会对斐波那契数列进行基准测试 创建一个基准测试用例 - Benchmark测试文件必须以filename加结尾 - 方法名必须以开头 - 测试参数必须为 1. 执行...

Go性能分析

golang中性能调试优化的方法包括: - benchmark:基准测试,对特定代码的运行时间和内存信息等进行测试 - profiling: 程序分析,程序的运行画像,在程序执行期间,通过采样收集的数据对程序进行分析 - Trace:跟踪,在程序执行期间,通过采集发生的事件数据对程序进行分析 > [...

Pprof

1. 使用方式 必须在代码里引入才能使用,不像Java里jdk工具包中的 、、、、 工具可以单独使用。可以从以下两个包中引入 Golang pprof的使用方式主要有两种 1. 在程序中通过http接口的方式暴露相应的pprof的采集控制界面,需要依赖, 使用 包来进行封装。 2. 可以用来产生du...

程序调优

进程、线程与协程

1. go中线程的数量 Go 使用Goroutine 调度器 (Scheduler) 来管理Goroutine的执行。调度器的核心概念如下 1.1. GMP模型 goalng采用特有的GMP模型。 1. G(Goroutine):指的是 Go 代码中的 Goroutine。 2. M(machine...

Go面向对象

1. 自定义数据类型 增强代码可读性 2. 方法接收器 只有自定义类型(或内置类型)才能够绑定方法,从而获得面向对象的特性。例如,为 绑定 方法。,使得User对象实现了Name方法。 在Go语言中,接口的实现是隐式的,也就是说只要一个自定义类型实现了接口中声明的所有方法,那么它就被认为实现了该接口...

反射

1. 任意类型 在 Go 语言中, 常被称为“空接口”,它的确能承载fff88f">任意类型的值,但这种“万能”特性背后存在明显的权衡和局限性。也可以用关键词替代, go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息 对于这个例子, 的类型信息是 ,数据信息是 ,这两部分信息都是存储...

泛型

1. 泛型 我们知道,函数的 形参(parameter) 只是类似占位符的东西并没有具体的值,只有我们调用函数传入实参(argument) 之后才有具体的值。那么,如果我们将形参 实参这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,问题就迎刃而解:在这里我们将其称之为 类型形参(typ...

Http连接池

1. 长连接 长连接利用keep-alive技术实现,能在多次 HTTP 之间重用同一个 TCP 连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等)。 然而长连接并非没有弊端,天下没有免费的午餐,如果客户端在接收完所有的信息之后还没有关闭连接,则服务端相应的资...

Java

Java9 Java17新特性

1. 集合 增加 了 List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。 2. 私有方法 java8中允许接口有默认方法,java9中允许接口有默认私有方法 3. Optioal的ifPresentOrElse 4. 类型推断 类型...

Jni

会在指定的所有目录下查找名为 libnativememoryutils.so(Linux)或 nativememoryutils.dll(Windows)的文件。 > [!note] > 在 Linux 下,文件名必须是 libnativememoryutils.so,不能省略 lib 前缀。 1....

Mtls实现

1. 生成证书和密钥库 1.1. openssl配置 openssl中可以通过--config指定完整的配置文件,包含所有配置段(sections),用于生成包含扩展字段的证书签名请求(CSR)或自签名证书。 1.2. 生成客户端证书和服务端证书 2. 本地安装ca证书和客户端证书 windows可...

Velocity

1. 新建module-a 引入maven依赖 自定义Annotation 继承AbstractProcessor,实现自定义Processor 2. 新建module-b 添加依赖module-A 类填写注解,以生成编译后的class。可以添加些属性,用于编译class时使用。也可以给多个类添加注...

内存管理

1. 总结 java应用的内存主要分为三块 1. jvm管理的堆内存 2. jvm管理的非堆内存 3. 非jvm管理的内存 1.1. JVM管理的堆内存(Heap Memory) 存储对象实例,是 Java 内存管理的核心区域,由所有线程共享。 - 新生代(Young Generation):存放新...

虚拟线程

1. 概念 1.1. 平台线程 我们常用的Java线程与系统内核线程是一一对应的,系统内核的线程调度程序负责调度 Java线程。为了增加应用程序的性能,我们会增加越来越多的Java线程,而由于多种因素,平台线程的数量受到了很大的约束。 - 资源有限导致系统线程总量有限,进而导致与系统线程一一对应的平...

Grpc

1. 工具推荐 - apifox:rpc客户端,可以导入proto文件,自动生成rpc客户端并重试 2. 通信过程 3. 抓包 4. protocol buffers Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数...

Rpc

4+1视图

| 视图类型 | 描述 | | :-------------- | :------------------------------------------------------------------------------------------ | | 逻辑视图 | 逻辑视图面向系统逻辑分析和...

DDD

DDD理论与实践

1. 什么是DDD DDD是一种处理高度复杂领域的ff0000">设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD不是架构,而是一种架构ff0000">设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域...

核心概念

通用模型 NAT网关模型 1. 领域 汉语词典的解释:领域是从事一种专门活动或事业的范围 、部类或部门。 百度百科的解释:领域具体指一种特定的范围或区域。 两个解释有一个共同点——范围,领域的核心重点落在ff0000">”域”字上,用来确定范围的,fff88f">范围即边界 ,这也是DDD在设计中不...

Design

代码坏味道

1. 重复代码 2. 长函数 3. large class 4. Divergent Change(发散式变化) 对程序进行维护时, 如果添加修改组件, 要同时修改一个类中的多个方法,那么这就是 Divergent Change。举个汽车的例子,某个汽车厂商生产三种品牌的汽车:BMW、Benz和La...

设计原则与典型架构设计模型

架构的设计本质都是为了高内聚、低耦合 1. SOLID原则 1.1. S单一职责原则(single responsibility) 一个class应该只做一件事,一个class应该只有一个变化的原因,核心是ff0000">功能特性解耦和ff0000">高内聚性。避免一个类承担两个特性,修改A特性的时...

1. 为什么要Dpdk(Data Plane Development Kit)

1. 基于OS内核转发的劣势 1.1. 数据路径长,协议栈处理开销大 - 内核路径: 数据包需经过内核协议栈(如TCP/IP栈)的多层处理(链路层→网络层→传输层→应用层),涉及大量内存拷贝、校验和计算、上下文切换等操作。 - DPDK优化: 直接绕过内核,在用户空间处理数据包,省去协议栈的逐层解析...

ConfigMap

1. 创建 1.1. 通过kubectl命令行创建 1.1.1. --from-file参数从文件中进行创建 其中key=是可选的,默认key就是文件名,通过key=可以指定key。 1.1.2. --from-file参数从目录中进行创建 目录下每个配置文件名都被设置为key,文件的内容设成为va...

Ingress Gateway

在Kubernetes的服务网格架构中,Ingress Gateway是通过Envoy实现的。在Istio中,Ingress Gateway 是用于管理进入服务网格的流量的组件,它基于 Envoy 代理构建。Envoy 是一个高性能的代理,专门设计用于处理服务到服务的通信,支持动态服务发现、负载均衡...

Kube Proxy和Istio Envoy

- 每个节点安装了一个kube-proxy - 每个pod以sidecar的形式部署一个envoy - kube-proxy拦截的是进出kubernetes节点的流量,envoy拦截的是进出pod的流量。 - istio作为服务网格的一种实现,本质上提供了应用间的流量、安全管理和可观察性。 1. k...

Kubernetes 核心概念

kubernetes中的基本概念和术语大多是围绕资源对象来说的,而资源对象在总体上可以分为以下两类: (1)某种资源的对象,例如节点(Node),Pod,服务(Service),存储卷(Volume) (2)与资源对象相关的事物与动作,例如标签(Label)、注解(Annotation)、命名空间(...

Pod调度

k8s Master上的Scheduler服务负责实现Pod调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通过我们无法知道Pod最终会被调度到哪个节点上。 1. NodeSelector 可以实现将Pod调度到一些指定的Node上,可以...

Service

一个service对外暴露一个,client可以访问这个来实现负载均衡访问后端Pod。一个service对应的“后端”由pod的ip和端口号组成,这在kubernetes中称为endpoint。一组endpoints形如,其中和都是podIp。

Postgresql DML

1. 常用运维sql

性能优化

1. 查看数据库CPU有没有冲高,一般是有慢SQL 2. 查看数据库I/O有没有冲高,如果有一般是用了外部排序 3. 优化外部排序有两个思路,一个是添加索引,避免临时排序;第二个是限制数据规模,尽量保证在内存中完成排序 4. 先联表后排序,索引会无法利用;要做利用索引做子查询排序,然后再联表 5. ...

数据库关键配置

1. socketTimeout 未配置socketTimeout时,应用不会超时,会永远等待。当出现网络隔离故障时,服务端访问数据库的连接会hang住,导致需要等到tcp重传失败或者keepalive失败才会断开连接,故障时间会非常长。 数据库连接池中的连接可能处于如下两种状态 - 连接处于繁忙状...

数据库理论

1. 三大范式 - 第一范式(1NF) 保证列的原子性,一张表的某个列不能有多个值,不能出现重复列。实际操作中我们可能会违反这个约束,如将列值存成一个json字典,但是我们一定要保证这些值至少是一起读或写的,不存在经常部分更新的场景。 - 第二范式(2NF): 取消部分依赖,表中的每个字段都与主键相...

Etcd

Etcd常见用法

1. 数据put和get 1.1. 设置key和value 1.2. 获取指定key 1.3. 获取前缀下所有key 2. 数据过期与续约 2.1. 创建租约并设置 TTL(Time To Live) 创建一个 TTL 为 60 秒的租约: 该命令会返回一个租约ID,例如 2.2. 查看所有leas...

Etcd架构

etcd 集群通过Raft算法实现了 “动态主从 + 分布式共识” 的架构,确保数据强一致性和高可用性。etcd 集群在同一时刻只能有一个主节点(Leader),且写操作必须由该主节点处理,但读操作可以从任意节点执行。etcd集群中的节点在正常运行时通过Raft算法动态选举 “领导者(Leader)...

Watch使用与原理

Watch机制是etcd的核心功能之一,它允许客户端监听指定键或前缀的变化,实现配置变更的实时通知。默认情况下,watch是从ff0000">最新版本开始监听的。 1. 使用方式 1.1. 基本使用 添加或修改key etcd提供了多种方式使用Watch功能 1.1.1. 命令行方式 1.1.2. ...

数据存储

1. 数据存储格式 etcd的数据存储格式主要是基于键值对的形式。 - 键:是一个唯一的字符串,用于标识存储的数据,类似于字典中的键。例如,在一个配置管理系统中,键可能是某个服务的名称加上配置项的名称,如。 - 值:可以是任意类型的数据,如字符串、整数、JSON 对象等,这取决于具体的应用场景。例如...

Kafka Stream

1. 流式计算和批计算 1.1. 流式计算 流式计算是持续地从数据源获取数据,并实时地对数据进行处理和分析。 1.1.1. 特点 - 实时性强,能够在数据到达时立即进行处理。 - 数据无边界,是持续产生的 - 计算模型有状态,需要维护中间状态,一般用增量计算代替全量计算 - 时延敏感,需要低延迟处理...

Kafka架构

1. topic kafka将消息以topic为单位进行分类,一个topic就是一个逻辑队列。类比于数据库中的分库。 2. partition 为了实现扩展性,提高并发能力,kafka将一个逻辑队列(topic)划分为多个partition,每个partition保存一个topic中的部分数据,每一...

Kafka核心配置项

这里我们以网络服务用的最多的kafka组件为例,来介绍下其实现原理,已经如何在知晓原理的情况下用好kafka。 1. producer - : 控制producer端开启幂等和事务能力。开启幂等后能够保证消息不重复。 - 为生产者提供了一个唯一的事务标识符。通过这个标识符,Kafka 能够跟踪和管理...

Kafka生产消息

1. kafka获取partition对应节点 通过元数据获取 - Kafka producer 在启动时会向 Kafka 集群中的任意一个 broker(通常是配置中的 bootstrap.servers 列表中的一个)发送元数据请求(Metadata Request)。这个请求用于获取集群的元数...

Kafka高可靠

1. 生产可靠 为保证Producer发送的数据,能可靠地发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送ACK(ACKnowledge 确认收到)。如果Producer收到ACK,就会进行下一轮的发送,否则重新发送数据。 1....

Kafka高性能读写

核心: - 顺序读写 - page cache,避免每次读取磁盘。也正是因为顺序读写,避免了大量page cache miss,可以充分利用page cache。 - 零拷贝。 1. 存储结构 一个topic下多个partition,每个partition使用一个文件夹存储。partition命名规...

不同消息队列对比

- RocketMQ 的存储结构相对较为灵活,可以针对不同的场景进行优化。它采用了单一的 commitlog 文件来顺序存储所有消息,然后通过索引文件快速定位特定topic的消息。这种设计使得在处理大量 topic 时,存储资源的分配更加高效。每个topic都对应独立索引文件,加上page cach...

云时代的消息中间件

消息队列核心

- 为什么需要消息队列 - 消息队列的核心设计和使用 - 深入消息队列高性能原理 cap原则 1. 为什么需用消息队列 1.1. 业务解耦 业务解耦,代码解耦。现在的云网络服务大都遵循SDN设计思想,将服务拆分成管控转三部分,不同组件负责承载不同的业务,其解耦方式之一就是通过消息队列。很多消息队列都...

Middleware

Redis

Redis关键参数

1. go-redis 在go- redis中,连接设置是通过 Options 结构体来管理的。 1.1. 单机模式 - Network - 网络类型,可以是 tcp 或 unix,默认值为 tcp。 - Addr - Redis 服务器的 host:port 地址。 - Dialer - 创建新网...

Redis命令

1. redis支持数据类型 - 字符串 - hash(key-value) - list(有序列表) - set(无序唯一集合) - zset(有序唯一优先级队列,每个元素关联一个分数,按从小到大排列) - Bitmap - HyperLogLog,占用内存很小(12kb)的情况,可以用于估算接近...

Redis批处理

1. pipeline Redis执行一条命令需要经历以下过程:、、、。由于Redis本身是基于协议(停等机制)的,虽然Redis已经提供了像 、 这种批量的命令,但是如果某些操作根本就不支持或没有批量的操作或者需要连续执行好几个不同命令,那我们就只能一条一条地执行命令,每执行一条命令都要消耗请求与...

Redis高可用

1. master和slave数据复制机制 在Redis集群模式下,Master和Slave之间默认采用异步复制 - 客户端向Master写入数据后,Master立即返回成功响应,无需等待Slave确认。 - 数据随后异步复制到 Slave 节点。 这种设计的目的是保证高性能和可用性,但存在数据丢失...

代码示例

发布订阅模式

Redis发布订阅简介 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis的SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。 作为例子, 下...

Zookeeper

分布式锁

1. 分布式公平锁 ZooKeeper的临时顺序节点,天生就有一副实现分布式锁的胚子。 1. ZooKeeper的每一个节点,都是一个天然的顺序发号器。 在每一个节点下面创建临时顺序节点(EPHEMERAL\SEQUENTIAL)类型,新的子节点后面,会加上一个次序编号,而这个生成的次序编号,是上一...

基本概念

1. zookeeper数据存储 ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如 /hbase/master,其中 hbase 和 master 都是 ZNode。每个 ZNode 上都会保存自己的数据内容,同时...

DCN

DCN架构演进

1. 服务与网络三大耦合问题 - 交换机堆叠,存在单点风险和运维困难问题 - 物理网络大二层隧道,引入更大故障域 - 硬件墙的引入限制整体云业务规模,硬件强性能不行,无法横向扩展 2. 名词解释 NSA(Network Service Area) - NWS(Network Service): 网络...

交换机堆叠

1. 背景 主机连接单个交换机,存在单点故障问题(单个交换机故障),解决tor交换机单点故障的思路有两个。 - 上层服务(计算、网络)保证高可用,当一个tor下主机故障时,可以快速在另一台tor下拉起新主机 - 两台tor交换机堆叠,保证tor交换机的高可靠 2. 概念 交换机堆叠一般是指被背板堆叠...

大二层架构的问题

1. 历史背景 网关在汇聚上的原因是因为openstack平面约束,要求资源池里面的节点必须在一个二层上。 管理服务区解大二层原因是管理区容灾1.0方案依赖vip在az间漂移,管理区汇聚下就是一个二层。同时,az之间还需要在两个az的汇聚交换机上通过硬件vxlan打一个二层隧道。 2. 大二层的问题...

(七)Linux下实现NAT

在NAT Overview一文中,我们介绍了在linux下通过iptables实现NAT功能的命令及原理。本文以容器为例,进行几个简单的实验来进一步了解linux下NAT的实现过程和应用。 1. nat在容器中的应用 1.1. SNAT linux下一个最典型的NAT应用就是docker容器借助宿主...

(九)高性能NAT

(五)NAT ALG

--- title: NAT ALG date: 2023-02-01 categories: - network tags: - network - nat --- 能够对特定的应用层协议进行转换,在对这些特定的应用层协议进行转换过程中,通过的会话信息来改变封装在报文载荷中的和端口信息,最终实现下...

(八)高可靠NAT

1. 双机冷备 2. 双机热备 双机热备可以极大地提升NAT服务的可靠性和承载能力,但是两台NAT设备上的NAT配置也需要完全相同。这样就会出现一个问题:如果两个NAT设备分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了优先级的概念。 在双机热备的环境...

(六)路由器配置NAT

本章节主要以cisco路由器配置为例,使用GNS3仿真平台搭建一个最简单的实验环境进行验证。 1. 常用命令(cisco) 首先介绍本实验可能用到的一些命令,先有个概念,后面再在实验中熟悉并掌握。 1.1. VPC 1.2. 路由器 2. 环境 2.1. 配置PC 2.1.1. PC1 2.1.2....

(十)NAT穿透

NAT高可靠

1. 双机热备 双机热备对于防火墙来说是一个必不可少的功能,两台防火墙上的NAT配置也需要完全相同,这样就会出现一个问题:如果两个防火墙分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了地址池优先级的概念。 在双机热备的环境中,如果地址池被配置为高优先级...

流日志

1. 格式 1.1. 网络信息7元组 internalip,internalport,externalip,externalport,transitip,transitport,protocol 1.2. 流量数据 packetnum,bytesize 1.3. 时间信息 starttime,end...

Network

IP分片报文和TCP分段报文

分组可以发生在传输层和网络层,传输层中的TCP会分段,网络层中的IP会分片。IP层的分片更多的是为传输层的UDP服务的,由于TCP自己会避免IP的分片,所以使用TCP传输在IP层都不会发生分片的现象。我们在学习TCP/IP协议时都知道,TCP报文段如果很长的话,会在发送时发生分段,在接受时进行重组,...

SCTP

SCTP ( Stream Control Transmission Protocol ),即流媒体控制传输协议,是一种可靠的基于无连接数据包网络如IP网络之上传输协议。他被设计用来在IP网络上传输PSTN在窄带信令消息,同时也能支持宽带信令消息的传输。 SCTP可以看作OSI层次结构中的传输层,它...

Tcp KeepAlive

1. 起源 连接有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接。长连接环境下,双方建立交互的连接并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现: 1. 主机突然掉电、死机、异常重启 2. 中间路由...

Tcp重传

1. tcp超时重传 重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的确认应答报文,就会重发该数据,也就是我们常说的超时重传。 TCP 会在以下两种情况发生超时重传: - 数据包丢失 - 确认应答丢失 1.1. RTT与RTO - RTT(Roud Tri...

Nat封装Pp协议

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements client ^pdsoIL0z NAT ^lEtOOR5s Server ^7HyKnicB SYN seq0 ^3...

源地址透传

1. TOA(TCP Option Address) TOA将源地址放在字段中。option字段最长40字节,每个选项由三部分组成:op-kind、op-length、op-data,我们最常见的MSS字段就是在option里。目前option使用的op-kind并不多,我们只需要构建一个不冲突的o...

DHCP协议

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,统一使用两个IANA分配的端口:67(服务器端),68(客户端),DHCP客户端使用的源端口号为68,目的端口号为67发送请求消息到...

Dns Zone

DNS Zone是指域名系统(DNS)中管理的一部分域空间,包含了该区域内所有域名的记录信息。它是确保网络中计算机能够通过域名解析到正确 IP 地址的关键组成部分,对于维持互联网的正常运行至关重要。一个DNS Zone通常由一个组织或个人负责维护,这个区域包括了域名及其子域的权威信息,如IP地址、邮...

DNS

DNS配置与匹配规则

1. multi dns ip 对于一个主机配置多个DNS IP 1.1. 作用 1.1.1. 冗余和容错 - 当第一个DNS服务器无法响应时,主机会自动尝试使用第二个、第三个DNS IP - 提高DNS解析的可靠性,即使一个DNS服务器宕机,仍可通过其他DNS服务器进行域名解析 1.1.2. 负载...

Https抓包

由于主流浏览器都只支持HTTP/2 Over TLS,也就是说当前HTTP/2网站都使用了HTTPS,数据传输都经过了SSL加密,常规抓包方法并不能看到明文数据。 1. wireshark Wireshark 的抓包原理是直接读取并分析网卡数据,要想让它解密 HTTPS 流量,有两个办法: 1)如果...

HTTP协议解析

1. HTTP 1.0 !http1.0抓包.pcapng HTTP/1.0 默认为每一对 HTTP 请求/响应都打开一个单独的 TCP 连接。 - 17945-17947:tcp三次握手 - 17948:Server告诉Client更新自己的接收窗口大小 - 17949:Client发起HTTP ...

TLS

1. TLS是什么 Transport Layer Security (TLS) 是一种被广泛采用的安全协议,旨在增强互联网通信的私密性和数据安全性。TLS的主要使用场景是对Web应用和服务器之间的通信(例如,Web 浏览器加载网站)进行加密。TLS 还可以用于加密其他通信,如电子邮件、消息传递和 ...

常用Http Header

1. remoteaddr 表示发出请求的远程主机的IP地址,remoteaddr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时 1. 假设中间没有任何代理,那么网站的web(,Apache等)就会把remoteaddr设为你的机器IP 2...

应用层

ARP

1. arp响应的条件 1.1. 普通主机响应 ARP 请求的条件 当本机是普通主机时,只有当 ARP 请求中的目标IP地址与本机IP地址匹配时,普通主机才会发送ARP响应。例如,主机A的IP地址为192.168.1.10,当它接收到ARP请求,且请求的目标 IP地址为192.168.1.10时,主...

VLAN

1. 介绍 当网络上所有设备在同一个广播域产生大量的广播和多播帧(arp/dhcp/stp/rip),就会与业务数据流争带宽,造成网络性能恶化。将大型广播域分段是提高网络性能的方法之一。路由器能够将广播包阻隔在一个三层接口上,但是路由器的LAN接口数量有限,它的主要功能是在三层网络间传输数据,而不是...

交换机原理

1. SAT(MAC地址表、FDB、CAM) 交换机的Source Address Table(源地址表),也称为MAC地址表、FDB(forwarding database)或CAM(Content Addressable Memory),是一种用于存储和管理设备MAC地址和端口之间映射关系的表格...

概念解释

ISP: Internet Service Provider 互联网服务提供商 IXP: Internet eXchange Point 互联网交换点 通信方式: - 客户-服务器方式(C/S) - 对等方式(P2P) 1. 电路交换 电路交换必须经过“建立连接(占用 通信资源)一通话(一直占用通信...

IPSec

IPSec (Internet Protocol Security) 协议主要工作在OSI模型的第3层网络层,为上层传输层协议(如TCP、UDP等)提供安全支持。IPSec不是一个单独网络协议,而是一系列为IP网络提供安全性的协议和服务的集合,提供了认证、加密、数据完整性等安全服务。 1. 主要协议...

仿真

Ipv4地址

1. 特殊ip地址汇总 - 0.0.0.0/8:用于广播信息到当前主机 - 10.0.0.0/8:用于专用网络中的本地通信 - 172.16.0.0/12:用于专用网络中的本地通信 - 192.168.0.0/16:用于专用网络中的本地通信 - 100.64.0.0/10:用于在电信级NAT环境中服...

IPv6地址

1. 地址格式 IPv6地址有128bit,在这128bit中,前64bit是网络前缀,后64bit是接口标识。在前64bit中,前48bit又是全球可汇总地址,在给一个公司分配IPv6地址时,总是分配给它一个前48bit固定的地址,而后面的16bit又可以被该公司用来做子网地址。这样分配,可以方便...

网络层

链路本地地址

链路本地地址(Link-local Address)是一类特殊的IP地址,仅用于在网段内、同一广播域内的主机相互通信使用,这类主机可认为是不需要外部互联网服务的。其中IPv4的链路本地地址定义在169.254.0.0/16范围内,IPv6定义在fe80::/10范围。链路本地地址在ipv4和ipv6...

BGP

BGP基础概念

1. AS - OSPF、IS-IS等IGP路由协议在组织机构网络内部广泛应用,随着网络规模扩大,网络中路由数量不断增长,IGP已无法管理大规模网络,AS的概念由此诞生。 - AS指的是在同一个组织管理下,使用统一选路策略的设备集合。 > [!question] 不同的AS之间需要进行通信,在AS之...

BGP邻居建立

- 先启动BGP的一端先发起TCP连接,R1先启动BGP,R1使用随机端口号向R2的179端口发起TCP连接,完成TCP连接的建立。 - 三次握手建立完成之后,R1、R2之间相互发送Open报文,携带参数用于对等体建立 - 参数协商正常之后双方相互发送Keepalive报文,收到对端发送的Keepa...

路由协议

CPU

1. 基本概念 - Socket: 指的是主板上用于安装物理CPU芯片的接口。每一个这样的接口能够插入一个物理的CPU处理器,一台计算机可以配备多个这样的插槽,进而安装多个物理CPU,以增强系统的计算能力和性能。一个物理CPU可以有多个物理CPU核。 - 物理核(): 可以看的到的,真实的cpu核,...

File System

常见文件目录

各大发行版基本一致,遵循的是通用的FHS(Filesystem Hierarchy Standard)约定,(个别子目录如 lib64、multiarch 名称可能略有不同) 1. 开发者部署模板 用一个假想应用myapp为例,展示其在Linux系统中应该如何分布文件和目录。 🏗️ myapp 部...

Linux

Bridge

1. 常见命令 2. 泛洪 - 泛洪机制:桥接设备会将该数据包从除接收端口之外的所有其他端口发送出去。这样做的目的是确保目的设备无论连接到哪个端口,都有机会接收到这个数据包。例如,在一个包含多个主机的局域网中,如果主机 A 向一个桥接设备发送了一个数据包,而该桥接设备的 FDB 表中没有目的主机的 ...

Geneve隧道配置

1. geneve协议格式 GENEVE与VXLAN类似,仍然是Ethernet over UDP,也就是用UDP封装Ethernet。VXLAN header是固定长度的(8个字节,其中包含24bit VNI),与VXLAN不同的是,GENEVE header中增加了TLV(Type-Length...

Network Namespace基础

可以创建一个完全隔离的新网络环境,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables等等。总之,与网络有关的组件都是独立的。 可以看到我们刚才创建的网络环境 进入虚拟网络环境,使用命令 只能看到lo口 这样我们可以在新的网络环境中打开一个shell。 连...

Route Table与VRF

在 Linux 系统中,支持使用多个 route table(路由表) 是为了增强网络配置的灵活性和功能性。多路由表的主要作用如下: 1. 实现策略路由(Policy Routing) 默认情况下,Linux 使用主路由表()中的路由规则来决定流量的转发路径。然而,某些场景下可能需要基于流量的源地址...

Tcp关键内核参数

1. tcpsynretries 这个参数值设置的是client发送SYN如果server端不回复的话,ff0000">重传SYN的次数。对我们的直接影响就是connet建立连接时的超时时间。当然Java通过一些C原生系统调用的组合使得我们可以进行超时时间的设置。在Linux里面默认设置是5,下面给...

Tun Tap介绍

在计算机网络中,TUN(Tunnel)与TAP(Test Access Point)是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。 - TAP等同于一个以太网设备,它操作第二层数据包...

Vxlan隧道配置

1. 主机应用通过绑定独立路由表或vrf的方式通过隧道访问对端 > 图为网图,本文配置vtep地址换成了10.0.0.250和10.0.0.251 250主机配置 查看当前系统信息 > 虽然从概念上讲,VRF 主要用于实现逻辑上的路由隔离,但在系统的网络配置和管理中,它被呈现为一个类似普通网络接口的...

管道

1. client 2. server 3. ouput 数据直接在内存中的管道缓冲区传输

网卡混杂

网卡在混杂模式下不会校验收到的报文的mac和ip

CPU使用率

1. 查看进程CPU使用率 1.1. 平均使用率 ps命令显示的值是进程的平均CPU使用率,计算公式为: $\%CPU=(进程使用的CPU时间​/进程运行的总时间)×100$ 如下命令显示CPU占用最高的前几名进程 输出如下 1.2. 瞬时使用率 如果想监控某个进程(比如 )的 CPU 使用率 ...

Ls

1. 基于文件名排序 2. 基于文件大小排序 3. 基于文件时间排序

Lsof

(List Open Files)是一个用于列出系统中打开文件的工具。这里的 “文件” 是一个广义的概念,包括常规文件、目录、块设备、字符设备、共享库、网络套接字等。会列出系统中当前用户打开的所有文件。 : 列出指定进程 ID 的进程打开的文件 :列出指定用户打开的文件 : 列出正在执行指定命令的进...

PS

命令是process status的简称,用于显示当前运行的进程的信息。在不使用任何标识的情况下,会显示所有当前用户启动的进程,比如: - PID: 进程的ID号 - TTY: 终端名称缩写 - TIME: CPU时间,即进程使用CPU的总时间 - CMD: 所执行的命令名称。 1. 参数 命令支持...

Sort

-f :忽略大小写的差异,例如 A 与 a 视为编码相同; -b :忽略最前面的空格符部分; -M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法; -n :使用『纯数字』进行排序(默认是以文字型态来排序的); -r :反向排序; -u :就是 uniq ,去重,相同的数据中,仅出现一...

常用运维命令

核隔离和Cgroup

!CPU1. 基本概念 1. 核隔离 修改内核启动参数 1.1. CPU加压测试 htop可以看到隔离的核没有受到影响 2. 绑定进程到隔离核心 3. cgroup 输出如下 > [!info] > Worker 0 started on CPU 2 Worker 2 started on CPU ...

Numa架构

是一种多处理器架构,它将系统中的内存分成多个节点,并将每个节点分配给不同的处理器。在 NUMA 架构中,每个处理器可以访问本地节点的内存,但访问远程节点的内存速度较慢。因此,NUMA 架构可以提高多处理器系统的性能和可扩展性。 下图为英特尔S2600系列服务器主板 - 两个CPU插槽,CPU插槽之间...

Os

Page Cache

文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被CPU访问。由于读写硬盘的速度比读写内存要慢很多,所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux内核使用页缓存(Page Cache)机制来对文件中的数据进...

中断

1. 中断(Interrupt)的定义 中断是计算机系统中一种关键机制,允许处理器暂停当前执行的任务,转去处理更高优先级的紧急事件,处理完成后恢复原任务。中断机制提高了系统的响应速度和资源利用率,是操作系统实现多任务、实时响应的基础。 2. 硬件中断(Hardware Interrupt) vs 软...

操作系统地址空间

操作系统的地址空间是一个抽象概念,表示操作系统在运行时能够管理和访问的内存范围。它提供了一种将物理内存映射到逻辑地址的机制,使得程序能够在一个独立于物理硬件的虚拟地址空间中执行。 1. 用户空间与内核空间 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所...

操作系统概述

1. 操作系统基本特征 1.1. 并发 并发是指两个或多个事件在同一时间间隔内发生,在多道程序环境下,一段时间内宏观上有多个程序在同时执行,而在同一时刻,单处理器环境下实际上只有一个程序在执行,故微观上这些程序还是在分时的交替进行。操作系统的并发是通过分时得以实现的。操作系统的并发性是指计算机系统中...

进程调度策略

进程调度的关注点: - 性能:执行完所有任务需要的总时间。 - 公平性:每个任务都期望自己先被执行,尽早完成。 - 响应时间:首次运行时间 - 任务提交时间。 性能、公平性、响应时间是矛盾的,为了达到最好的性能,我们希望cpu不要在不同进程或线程之间切换,这样避免了上下文的切换,一个任务从开始执行到...

Openssl

1. RSA密钥 生成私钥 - :指定算法为 RSA - :指定密钥长度为 2048 位(可改为 4096 - :输出私钥文件路径 生成公钥 - :输入私钥 - :导出公钥 - :输出公钥文件路径 2. ECC密钥 生成对应的ECC私钥 - :指定椭圆曲线(可换成 , , , 等) - :生成密钥 ...

SSL证书

在密码学和数字证书体系中,一般没有“私钥证书”的说法。主要是公钥证书。公钥证书(通常就简称为数字证书)是一种电子文档,包含以下信息 - 公钥信息 - 持有者身份信息(如姓名、组织等) - 证书颁发机构(CA)的数字签名等 其主要目的是将公钥与特定的实体(如个人、服务器等)绑定,用于在网络通信等场景中...

Webauthn

在数字时代,密码已成为我们日常生活和在线活动中不可或缺的一部分。尽管互联网已经发展了20多年,许多方面都有了巨大的改进,但只有密码,还是20年前的用法。更准确的说,它的用户体验比 20 年前更差了。 1. 密码的强度现在要求越来越高,一般不能少于8个字符,还要包括特殊符号。 2. 除了密码,通常还有...

Tech

CPU架构

1. 主频 CPU 主频,即CPU的时钟频率,是衡量 CPU 运算速度的重要指标,以下从其定义、工作原理及与性能的关系等方面进行具体解释。 - 基本定义:CPU 主频表示 CPU 内数字脉冲信号振荡的速度,单位为赫兹(Hz)。通常所说的CPU 主频为2.4GHz、3.6GHz等,意味着CPU内部的时...

CPU缓存设计

1. 缓存 Cache Memory也被称为Cache,是存储器子系统的组成部分,存放着程序经常使用的指令和数据,这就是Cache的传统定义。从广义的角度上看,Cache是快设备为了缓解访问慢设备延时的预留的Buffer,从而可以在掩盖访问延时的同时,尽可能地提高数据传输率。 快和慢是一个相对概念,...

CPU调度策略

1. MLFQ(Multi-Level Feedback Queue) - 系统中存在多个就绪队列,每个队列有不同的优先级,高优先级队列中的进程会优先获得 CPU 执行。 - 进程在队列之间可以移动,根据其行为(如 CPU 占用时间、I/O 操作等)动态调整优先级。 1.1. 规则 1.1.1. 优...

Blog Online

TODO

- BGP实践,BGP PEER建立 - 核隔离与CGROUP - NUMA - 中断 - 硬件卸载 - 大页 - etcd 1. cloud wan ccn一个vpc支持创建几个attachment ccn路由同步,会同步到未关联的attachment吗?默认会同步所有路由表吗?

Icloud

使用土区账号购买50G icloud,平均每个月2.5块 1. 土区账号登录icloud 2. 国区账号登录apple store 3. 闲鱼购买土区礼品卡充值到土区账号 4. 土区账号购买icloud 5. 过去账号通过airdop发送邀请土区账号加入家庭组 6. 土区账号共享icloud设置家庭...

Excel

1. 将多个sheet join合并 Power Query自动化合并(适合多列/大数据) 1. 导入数据到Power Query: - 在Excel中点击数据 > Power Query > 获取数据,加载Sheet1和Sheet2。 2. 合并查询: - 选择Sheet1作为主表,与Sheet2...

Vnc远程桌面

总览

Clash For Linux

Jupyter安装

z从sqlite官网,下载安装最新版本sqlite3 清理旧版本的sqlite3 安装并编译python环境 配置jupyter lab 添加python环境 1. 格式转换

Movie Pilot

1. docker 安装 注意要通过环境变量配置代理,否则无法访问themoviedb或thetvdb等 2. 下载与整理 可以通过软连接目录的方式达到下载目录和mp的/media目录一致。 3. ref https://wiki.movie-pilot.org/zh/install

Movie Bot

NAS搭建

1. 主机硬件 - 主板:微星B360M Mortar - CPU:i3-8300t - 内存:金士顿骇客8G 2 DDR4 - SSD:三星980 NVMe PCIe3.0 500G - 西数:红盘4T - 散热器:利民AXP90 X47 - 电源:台达VX350 - 机箱:先马米立方matx 2...

Openwrt

Ubuntu系统备份

1. 备份 2. 还原 如果当前启动无法启动,可以通过live cd来启动并执行恢复操作

Xiaoya

windows下必须使用bridge模式 1. docker安装 2. 定时清理 3. 获取元数据 在wsl的ubuntu子系统中执行 4. 参考

Advanced Table

obsidian 功能 - 自动化格式表格 - Excel样式的表格导航,即使用Tab和Enter在行和列之间导航 - 对指定的列进行函数求值 - 添加、删除、移动行和列 - 设置列的对齐方式 - 对指定列进行排序 - 将表格导出为CSV格式 公式 基本格式如下: 如中的 代表最后一行、第二列,右边...

Appearance

打开笔记仓库的 .obsidian 文件夹,其中如果没有 snippets 文件夹则创建。在 Obsidian 中的 CSS snippets 都是以 .css 的档案格式储存在特定的文件夹。如果你有将 CSS snippets 放到该文件夹,就会在 Obsidian 显示开关。 1. 别人给你的,...

Dataview

1 查询依据 yaml数据/metainfo 2 使用查询语言 使用下列语法创建查询语言代码块 dataview query command 3 使用内联查询 内联查询的结果只能是一个,不能查询一个列表。当前页面可以通过获得,其他页面可以通过双链语法获得 通过下列语法创建内联查询: 此博客文件名: ...

Excalidraw

obsidian 是一个手工风格的白板工具。可以使用呼出命令菜单,输入excalidraw进行创作。 library 提供了很多公开的模板库可以帮助我们画出很多精美的图案。 导出 可以导出为png或svg 双链 鼠标右键选中create link,可以在excalidraw中使用双链。和在markd...

Image Auto Upload Plugin

obsidian 使用aliyun oss作为obsidian图床 1. 下载 2. 配置oss作为图床 3. 在obsidian中粘贴图片后自动上传aliyun os

Mind Map

obsidian 1. 使用方式 使用呼出命令行,输入通过提示补全命令 2. pin 可以将思维导图的预览面板嵌到当前笔记中。 3. copy screenshot 将svg格式mind map复制到剪切板 4. bug修改 mind map已经很久没有维护了,对于代码块支持存在bug,会导致无法生...

Minimal Theme Setting

obsidian 设置主题 settings -> options -> appearance里面选择主题 Style settings 自定义包括字体等各种样式 Minimal Theme setting 里面内置了一些经典的配色,可以对主题进行一些快速设置

Obsidian Tasks

任务管理 常用语法 1. 今日之前(包括)已完成,done before 2. 本周截止当前日(包括)已完成,done after 3. 根据重要性排序,sort by priority reverse 示例 获取本周所有已经完成的任务,按优先级倒序排列 tasks done before done...

Obsidian总览

obsidian 1. 视图 obsidian一共提供了三种视图: - preview mode:预览模式,可以编辑,显示实时预览结果,此模式下metadata不会渲染 - reading mode:阅读模式,markdown渲染后结果,不可编辑 - source mode:以纯文本形式显示mark...

代理

ssh代理

效率工具

1. 添加环境变量 2. windows命令行代理 3. git代理 也可以直接编辑/.gitconfig文件 4. windows配置beyondcompare作为gitdiff 修改.gitconfig配置文件 5. 删除重复文件

Vault

Tcp异常断链

1. 现象 客服访问建行业务偶现超时。 建行server端主动发起断链,发送fin报文,客户B收到后回ACK报文,进入fin-wait-2状态 客户B服务端前设置有防火墙,fin状态下设置10秒超时,客户B超时后的rst报文被防火墙丢了,建行server无法收到rst报文,导致一直处于fin-wai...

业务偶现超时

1. 现象 客户A通过NAT网关访问客户B业务偶现超时。 2. 抓包分析 中间设备抓包发现,服务端主动断链,发送FIN报文并收到ACK报文完成两次挥手,但客户端并没有随之进行被动关闭,直到约20秒后发出rst报文,在此期间连接一直处于半关闭状态。而NAT网关fin超时时间为5秒,此时会话已经老化,再...

交换机选型要点

交换机选型

clos架构

CLOS网络的核心思想是:用多个小规模、低成本的单元构建复杂,大规模的网络。简单的CLOS网络是一个三级互连架构,包含了输入级,中间级,输出级。下图中的矩形都是规模小得多的转发单元,相应的成本也很低。简单来说,CLOS就是一种多级交换架构,在输入输出增长的情况下,增加中间交换单元的数量,而不是每个交...

NAT分片报文

前文我们聊到,在NAT地址转换中,NAT除了对IP地址转换外,还使用到TCP或UDP报文的端口号、ICMP报文的ICMP头中的identifier字段信息。当一个IP分片报文和TCP分段报文 1|IP报文被分成若干片之后,这些信息只有首片报文会携带,后续分片报文依靠报文ID、分片标志位、分片偏移量依...

NAT之ICMP

报文没有类似于或的端口信息,而正常NAT为了内网的安全性和IP地址的高利用率,一般都使用了五元组来匹配NAT会话。为了对ICMP的请求报文进行NAT映射,又要确保网络的安全性,只允许合法的ICMP响应报文进入内网,就必须对其进行特殊的处理。 1. ICMP请求和应答报文 ICMP的request和r...

NAT会话

我们知道传输层的任意一条流都是通过两个建立的,由组成,因此一条流可以用五元组表示。这个五元组中的任意一个元素都不能改变,否则就是另外一条流了。对于服务端来说,和中任意一个变了,就意味着一个新的接入连接;而对于客户端来说,和中任意一个发生变化,访问的就是一个新的服务,比如通常是一个服务,是一个数据库的...

Thrift协议

是一种轻量化、语言无关的RPC框架。主要包含三大部分:代码生成、序列化框架、RPC框架。 Thrift网络协议栈 采用的是模型,网络协议栈从下到上分别为:、、、。 Transport 传输层为网络提供了一个简单的抽象,定义了具体的网络传输协议。其本质是一个提供了通过各种协议进行通信的框架,支持包括和...

shell编程

本文主要介绍shell编程的基本语法以及实际应用中的常见命令。 1. 注释 1.1. 单行注释 1.2. 多行注释 多行注释也可替换成或 2. 变量 2.1. 变量定义 使用的形式,VALUE如果是字符串的话,可以使用单引号、双引号或者不加引号。单引号内的任何字符都会原样输出,不能进行转义,单引号内...

netstat命令

是基于Netstat这个命令行工具的指令,它可以用来查询系统上的网络套接字连接情况,包括,以及。另外它还能列出路由表,接口状态和多播成员等信息。 1. 参数选项 | 参数 | 作用 | | | --- | -----------------------------------------------...

系统信息查看命令汇总

在系统下经常要查看各种信息,需要使用到各种命令,全部背下来也是一项不小的工作量。每次临时找也比较浪费时间,这里汇总下常用的命令。 1. 系统 1.1. 查看linux内核版本 文件系统不是普通的文件系统,而是系统内核的映像。也就是说,该目录中的文件是存放在系统内存之中的,它以文件系统的方式为访问系统...

数据库隔离级别

本文主要目的是阐明数据库的四种隔离级别以及在mysql下实现隔离的原理。 1. 隔离级别 数据库事务隔离级别从低到高分别是:read uncommitted、read committed、retpeatable read、serializable,依次解决了数据库脏读、不可重复读、幻读问题。 - 脏...

用户和权限

使用 Linux 系统,不免会和用户和权限打交道,本文介绍了根权限和文件的权限属性等概念和应用。 1. 用户与群组 使用查看所有用户 使用查看所有用户组 修改文档所有者或群组 2. Linux权限 命令 - 代表三种身份owner/group/other,a代表全部身份all - 代表三种操作行为(...

进程管理

在 系统中,进程是资源调度的最小单位,进程的管理关乎着你使用系统的体验。 1. 进程类型 Linux 系统里有几种不同类型的进程:用户进程(User processes)、守护进程(Deamon processes)和内核进程(Kernel processes)。 1.1. 用户进程 系统里大多数进...

NAT概览

1. 背景 地址使用4个字节进行存储,最多能够提供个地址。随着互联网尤其是物联网的发展,全球地址早已不够用,因此人们发明了(网络地址转换)来缓解这个问题。 简单来说,国际互联网组织划分了三个网络地址段作为内部网络本地通信使用,分别是,,。大部分内部机器都使用这些网段中的私有地址,如果它们需要访问公网...

NAT Overview

1. What is NAT? 1.1. NAT(Level 4) NAT(Network address translation)即网络地址转换,工作在OSI模型的三层或四层(PNAT),用于修改IP数据包中的IP地址和端口。当在专用网内部的一些主机本来已经分配到了local ip地址,但又想和I...

iptables

iptables 是 Linux 防火墙工作在用户空间的管理工具,是 netfilter/iptablesIP 数据包过滤系统是一部分,用来设置、维护和检查 Linux 内核的 IP 数据包过滤规则。 1. 四表五链 数据包在经过每个的时候会按照每个链对应的表依次进行查询匹配执行的操作,如PRERO...

tcpdump

tcpdump是一个优秀的网络分析工具,提供了强大且简单的接口。 1. options - -i any 监听所有的网卡接口,用来查看是否有网络流量 - -i eth0 只监听eth0网卡接口 - -D 显示可用的接口列表 - -n 不要解析主机名 - -nn 不要解析主机名或者端口名 - -q 显...

数据中心网络架构

0.1. 数据中心 根据维基百科释义,指用于安置计算机系统及相关部件的设施,例如电信和存储系统。数据中心是全球协作的特定设备网络,用来在internet网络基础设施上传递、加速、展示、计算和存储数据信息。 0.2. 传统数据中心网络架构 如图1所示,传统的大型数据中心网络通常采用三层架构。cisco...

应用层DNS协议

(Domain Name System)域名解析服务采用架构,是一个应用层协议。的作用是将人类可读的域名(如:www.shinerio.cc) 转换为机器可读的 IP 地址(如:1111.111.111.111)。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。协议建立...

计算机网络(九)—— 传输层

传输层架构在网络层之上,在两台计算机进程之间传输数据,常见的传输层协议包括TCP和UDP。 1. TCP 1.1. 首部格式 1.2. TCP状态机 TCP是面向连接的,在其生命周期会有各种不同状态 | 状态 | 描述 | | ------------ | ---------------------...

路由协议

路由是选择路径并将报文沿着选择的路径进行转发的过程。 1. 路由器 1.1. 路由器功能 路由器从功能上可以划分为: - 路由选择:使用路由协议(或手工静态配置的方式)获取整个网络的拓扑结构,构造、维护路由表。 - 分组转发:分组转发结构由交换结构、一组输入端口和一组输出端口构成。 1. IP分组检...

计算机网络(七)—— 网络层 —— ICMP

互联网控制消息协议(Internet Control Message Protocol,ICMP)是TCP/IP协议族的核心协议之一,用于IP协议中发送控制消息,提供可能发生在通信环境中的各种问题反馈。通过这些消息,使管理者可以对所发生的的问题作出诊断,然后采取适当的措施解决。ICMP可以简单认为是...

数据链路层概述

数据链路层使用的信道主要分为以下两种: - 点对点信道,使用一对一的点对点的通信方式 - 广播信道,使用一对多的广播通信方式,广播信道上连接的主机很多,因此必须使用专用的共享信道协议来协调这些主机的数据发送。 0.1. 数据链路 当需要在一条线路上传送数据时,除了必须有一条物理线路外,还必须有一些必...

计算机网络(三)—— 网络的基本分类

本文主要介绍计算机网络的分类以及局域网技术。 0.1. 网络分类 - 地理位置: 1. 个域网(PAN,Personal Area Network)。个域网允许设备围绕一个人进行通信。一个常见的例子是计算机通过无线网络(蓝牙)与其外围设备(显示器、键盘、鼠标。打印机)连接。 2. 局域网(LAN,L...

计算机网络(二)—— 性能指标

计算机网络的常见的性能指标有速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率。 1. 速率(bit/s或byte/s) 速率是物理层概念,指的是信道上每秒钟传输的0/1比特数量。比如下载文件中显示的7.8MB/s指的是每秒中可以传输7.8M字节的信息。 2. 带宽(bit/s) 带宽是逻辑概念...

计算机网络(一)—— 分层模型

计算机网络按照使用范围可以划分为个域网、局域网、城域网、广域网和互联网,其组成复杂,存在着大量诸如集线器、交换机、路由器、笔记本、手机等硬件。为了降低网络设计的复杂性,现阶段大部分网络都会形成一个层次栈结构,每一层都建立在其下一层的基础之上,向上一层提供特定服务。这种分层结构的概念其实和计算机领域的...

加密算法

本文主要介绍密码学中常见的两种加密算法—对称加密和非对称加密。 1. 散列(摘要)算法 在学习加密算法之前,我们先来了解一下散列算法(散列不是加密)。散列算法是通过一定方式对原文进行计算,产生一个哈希值,不管原始数据是什么样的,得到的哈希值都是固定长度的,其作用只是为了验证数据的完整性和唯一性,无法...

© 2026 shinerio. All rights reserved.

hello' ]` |\r\n\r\n```shell\r\nif [ 'hello' = 'hello' ];then echo 'hello';fi\r\n# hello\r\nif [ 'hello' != 'ello' ];then echo 'hello';fi\r\n# hello\r\nif [ -z '' ];then echo 'hello';fi\r\n# hello\r\nif [ -n 'hello' ];then echo 'hello';fi\r\n# hello\r\nif [

Github Oauth

▐▛███▜▌ Claude Code v2.1.34 ▝▜█████▛▘ Opus 4.6 · Claude API ▘▘ ▝▝ C:\workspace\code\shinerio.github.io > 1. 文章详情页,点击划词后,点击使用github登录,页面直接404,需要修复 2. 底...

BGP Speaker

在传统的网络设备中,我们通常直觉地认为“一台设备 = 一个 BGP 进程 = 一个 Speaker”。但在现代网络设计和高级设备架构中:一台物理路由器可以拥有多个 BGP Speaker。 为了理解这一点,我们需要区分“物理实体”和“逻辑协议实例”。以下是几种实现“多 Speaker”的典型场景: ...

BGP路由器分类

在BGP(边界网关协议)中,路由器的分类通常根据其在自治系统(AS)中的位置以及建立邻居关系的方式来划分。 1. 按协议划分 1.1. EBGP 路由器 (External BGP) 当两台运行 BGP 的路由器处于不同的自治系统(AS)时,它们之间建立的邻居关系称为 EBGP。 - 位置: 通常位...

Awesome Tools

1. 全平台 - freefilesync,文件夹同步工具 - ChatGPT流媒体解锁检测脚本: - https://gitlab.com/fscarmen/warp解锁应用端chatgpt 2. macos 2.1. 媒体工具 - snipaste截图软件 - kap屏幕gif录制工具,对于博客...

Claude Code

1. claude code配置 1.1. ide集成 在vscode中安装claude code插件,然后在claude code命令行界面使用即可连接到vscode。claude code就可以和vscode进行交付,感知你在vscode中选中的代码、文件,claude code的修改也会在vs...

Subagents

1. 子代理是专门处理特定类型任务的 AI 助手,当Claude遇到与子代理描述相匹配的任务时,它会将任务委派给该子代理,由其独立工作并返回结果。 - 每个子代理都在自己的上下文窗口中运行 - 拥有自定义的系统提示词(System Prompt) - 特定的工具访问权限和独立的权限设置。 优势: -...

Cloudflare免费Worker

cloudflare一段时间之前推出的一项免费服务, 允许在CDN服务器上运行js脚本或wasm 截止到这篇文章写完的时候,这仍是一项长期免费服务,免费套餐为每天 100000 个请求, 大概是100个人 每人请求100次, 或0.01个人 每人请求10000000次

Quantumult X

1. 参考配置 https://raw.githubusercontent.com/limbopro/Profiles4limbo/main/full.conf 2. 规则仓库 https://github.com/blackmatrix7/iosrulescript/tree/master 3. ...

Cloud Wan竞品分析

1. 腾讯 - CCN路由同步,默认会同步所有路由表 - - VPC多条路由冲突的时候,可以支持启用、停用 1.1. 跨地域流量管理 - 单向的流量调度规则限速带宽总和不得超过带宽上限。 - 带宽上限提高后,默认规则的限速带宽不会自动调整,需手动调整 2. 阿里 - CEN路由同步,默认只会同步默认...

IBGP与EBGP区别

1. 使用场景 eBGP主要用于: - 不同运营商/组织之间交换路由,例如中国电信与中国联通的互联互通 - 企业多归属(multihoming)接入多个 ISP - IXP(互联网交换点)中各参与方之间的路由交换 iBGP 主要用于: - 在一个大型 AS 内部传递从 eBGP 学到的外部路由信息,...

BGP协议优先级

1. 核心逻辑 1.1. “大”即是好的(高优先级) Weight 和 Local Preference:这两个属性是管理员手动干预的首选。数值设置得越大,代表你越“偏好”这条路径。 1.2. “短/小”即是好的(低开销) - AS-Path 长度:这体现了 BGP 的路径矢量特性,跳数越少代表路径...

Openspec

A "change" in 是一个“承载着围绕一项工作所进行的所有思考和规划的“集合。文件夹位于,包含proposal, specs, design, tasks。 工作流程 目录结构 - - 这是最重要的目录,存储了系统当前是如何运行的完整描述。 - 按domain组织:为了防止单个文档过大,它按...

Claudemd

| 类型 | 位置 | 范围 | 版本控制 | 典型用途 | | ------ | ----------------------- | ---- | ----- | ------ | | 全局 | | 所有项目 | 不共享 | 个人编码偏好 | | 项目 | | 当前项目 | 共享给团队 | 团队规...

SDD(Spec Driven Develop)

Kiro的spec流程被设计为三个步骤:需求 (requirements.md) → 设计 (design.md) → 任务 (tasks.md)。每个工作流步骤都由一个Markdown文档表示,Kiro会引导你达成这三个步骤。 1. 需求文档 它被构建为一个需求列表 1. 每个需求代表一个“用户故...

Run Code

runcode是一个来自veadk库的内置工具,它提供了一个安全的代码执行沙箱功能。这个工具允许AI Agent运行用户请求的代码片段(主要是 Python3),并返回执行结果。该工具通过字节跳动云服务的API在远程安全环境中执行代码,并具有会话管理和身份验证功能。 1. 工作原理 1.1. 工具注...

Demo

1. summary 2. 3. 对话前,无任何相关记忆 通过对话告知喜欢滑雪 再次查询记忆库 4. 5. 部署MCP服务的时候可以选择API KEY自动生成MCP的入站身份鉴权 agentkit一直无法拉起mcp工具集,这里使用claude code本地测试 6. 环境变量参考 7. 相关链接 -...

Awscli

install 配置账号 1. ref https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

Invoke

调用需要具备以下权限

Introduce

Amazon Bedrock AgentCore 同时具备框架无关性和模型无关性,让你能够灵活、安全且大规模地部署和运行高级 AI 智能体。无论你是使用 Strands Agents、CrewAI、LangGraph、LlamaIndex 或任何其他框架构建智能体,也无论你在哪个大语言模型(LLM)...

Gateway

1. v2n agent runtime需要的short-term memory需要通过v2n连接到用户vpc中的postgresql(内存中易失)

AgentCore

Amazon Bedrock AgentCore是用于构建、部署、高效运行Agent的最先进的智能化平台,其服务包括Runtime, Gateway, Memory, Identity, Observability, Browser, Code Interpreter, Evaluation, po...

Oauth2.0

OAuth 2.0是一个关于授权(Authorization)的开放标准。它允许用户让第三方应用访问该用户在特定服务上存储的私有资源(如照片、联系人等),而无需将用户名和密码提供给第三方应用。最常见的例子就是:你使用“微信登录”或“GitHub登录”来注册一个新的网站,而不需要在该网站重新设置密码。...

记忆

1. 短期记忆 LLM的核心架构在推理时,每一轮都是独立的计算过程。因此如果不把所有对话历史都带上的话,LLM就只能针对当前问题就会回答,无法感知历史对话。 在AI中,短期记忆通常指“对话上下文”,包括: - prompt - 对话历史 - 前序大模型推理结果 - 前序工具执行结果 短期记忆有两种存...

开源模型汇总

1. | Model | Total Params | Activated Params | Context Length | | :--------------: | :---------------: | :-------------------: | :----------------: | ...

Uv

1. 设置pip源 配置环境变量 2. 设置cache位置 3. 初始化项目并指定最低python版本 4. 全局安装tool 如果安装Python包是为了在终端任何地方运行它的命令(比如 , , 或者你提到的 ),可以使用 : - 效果: 它会为这个工具创建一个隐藏的独立环境,但把它的可执行命令软...

MCP Gateway

1. ref https://www.volcengine.com/docs/86681/1844858?lang=zh

Agentkit

1. 支持协议: AgentKit智能体运行时支持A2A、MCP、标准HTTP三种通信协议 2. agent访问方式 - 公网访问:默认访问方式。 - 私网访问:选择同地域中的任意一个VPC和子网,每个可用区支持最多选择一个子网。 3. 大模型 通过api endpoint对接的是火山方案 3.1....

Huoshan

基本概念

1. 参数大小 | 缩写 | 英文全称 | 中文含义 | 数值(科学计数法) | 对应中文单位 | | ------ | ------------ | -------- | ------------- | ----------- | | M | Million | 百万 | $10^6$ | 100...

Bedrock

1. Model catalog - Amazon Bedrock Foundation Models - Amazon Bedrock Marketplace - Bedrock Custom Model Import 1.1. 区别对比 | 特性 | Foundation Models | Ma...

Awesome Tools

:pdf论文翻译,提供中英文对照。

Claude Desktop通信过程

MCP

MCP与AI Agent

AI Agent向LLM “提供” 了它能使用的工具列表,这个过程通常通过以下两种方式实现: 1. 静态工具注册(Static Tool Registration): - 在设计 AI Agent 系统时,开发者会预先定义并注册一系列可供 Agent 调用的工具。 - 每个工具都有一个清晰的 描述 ...

MCP开发原理

1. MCP Server开发 1.1. 注意事项 - 使用stdio作为transport layer的时候,不要进行任何控制台输出 1.2. 交互过程 INITIALIZE = "initialize" - 协商协议版本 - primitives支持情况,如tools、resources、pro...

MCP概念理解

1. 基本概念 MCP(Model Context Protocol 模型上下文协议) 是一个开放的标准化协议,用于在AI模型和外部数据源、工具之间建立安全、可控的连接。它定义了AI系统如何访问和利用外部上下文信息的规范。MCP就像是AI应用程序的USB-C接口,为AI模型提供了一种标准化的方式来连...

MCP配置

1. 基本定义 - 每个MCP服务器都是一个独立json对象,以服务器名称作为key - key在MCP配置文件中以及全局配置文件和项目配置文件中必须是唯一的 - 每个MCP服务器条目对象都必须具有属性 2. local mcp server - (可选):在执行之前,先将进程的工作目录切换到指定路...

开发与测试

1. tools 1.1. stdio 启动命令 inspector调试 mcp config 1.2. SSE 启动命令 inspector调试 mcp config 1.3. streamable http mcp-server仅需修改transport即可 mcp confi需要将修改为,例如...

Prompt

概念

1. Message类型 System message 在大模型内部是每次加在了用户输入的前面。在 的大模型设计的时候,有三种不同的message 类型,这三者是有明显区别的。 - System Message:对大模型的角色进行定义,并输入一些基础的指令,包括大模型的身份、一些用于提高安全性的指令...

RAG

RAG(Retrieval-Augmented-Generation, 检索增强生成)is a process that helps AI models "look things up" before they answer, like accessing my calender or the we...

Vector Database

Embeddings用数值形式的向量,在高维空间表示数据(通常是文本等非结构化的数据)。传统的关系型数据库并不适合存储和搜索这些向量表示。 向量存储库能够使用相似度算法对相似向量进行索引和快速搜索,使得应用程序能够在给定目标向量的情况下找到相关向量。 例如,在个性化聊天机器人的案例中,用户会向生成式...

AI

Agntcy

1. OASF Open Agentic Schema Framework一个基于OCI(Open Container Initiative)的可扩展数据模型,用于描述agent的属性并确保agent的唯一标识。OASF支持描述A2A代理和MCP服务器,并且可以扩展以支持其他常用格式,例如Copil...

Claude Skills

1. 原理 claude skill是一类模块化能力组件,用于拓展 Claude的功能边界。每项技能都封装了: 1. 元数据 2. 指令说明 3. 可选配套资源(脚本、模板) Claude Agent Skills的设计哲学在于模块化与按需加载,旨在解决传统代理系统中常见的上下文冗余、性能衰减以及操...

CrewAI

1. install https://docs.crewai.com/en/installation

执行计划

1. explain和explain anayze 和生成的执行计划通常是一致的,但并不能保证完全一致。 1.1. 核心功能对比 | 对比维度 | EXPLAIN | EXPLAIN ANALYZE | | -------- | ------------- | --------------- | |...

Excalidraw使用示例

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements %%>>>text element-link:Excalidraw<<<%%shinerio's blog ^0js7...

锥型NAT和对称型NAT

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== %% Drawing %%

Mysql可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

Mysql当前读下幻读问题

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Excalidraw Data Text Elements 事务A ^skXrctjq 事务B ^eJoYKov2 insert into ord...

Mysql当前读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^yULSUGNg 事务B ^RjMMNDeM select from orders where 10 sel...

Mysql快照读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^b7TiLH2p 事务B ^bMLs1cuE select from orders; ^nEabCTcv i...

Postgresql不可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

A2A

协议是一项开放标准,旨在解决人工智能快速发展领域中的一个基本挑战:由不同团队构建、使用不同技术且归不同组织所有的AI Agents,如何有效通信与协作? 随着人工智能代理变得更加专业化和强大,它们在复杂任务上协同工作的需求也日益增长。想象一下,用户让其主要的人工智能助手规划一次国际旅行。这一个请求可...

AI Agent

AI Agent综述(Chatbot + Workflow + AI Agent + Function Call + MCP)

1. LLM E.G. Chat GPT, Google Gemini and claude. 1.1. Work Mechanism Human provide an input and the LLM responds with an output. > input(prompt) -> LLM...

设计模型(Work Flow & Agent)

1. 工作流模式(Predefined Workflow) 1.1. 流水线 流水线是最简单直接的工作流编排,通过编排一个顺序处理的流程,让模型逐步执行和推理。 场景示例: 文档处理系统:文档上传 → 格式转换 → 内容提取 → 语义分析 → 结果存储 1.2. 路由分发 路由分发是将输入分类(LL...

AI Code

AI Platform

Aws

Dify

1. chatbot - 核心就是对话 - 支持自定义System prompt - System prompt和Conversation opener支持jianjia变量渲染 - 支持rag 输入变量后,进行对话 2. Agent Agent相比chatbot增加了工具调用的能力 2.1. Co...

字典树(Trie)

1. 单级字典树 字典树最基础的应用——查找一个字符串是否在「字典」中出现过,也可以用来做最长前缀匹配。 如下图,每个路径代表一个字母,每个节点存储以该节点结尾的字符串是否存在,构建如下的一个字典树。 1-4-8-12,且12节点记录值为true,则代表存在这样的路径的字符串,即存在caa字符串。 ...

时间轮

时间轮(Timing Wheel)是George Varghese和Tony Lauck在1996年的论文实现的,是一种实现延迟功能(定时器)的精妙的高级算法,其算法应用范围非常广泛它在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一,在Java开发过程中常用的Dubbo、Net...

Dapr(Distributed Application Runtime)

dapr重点落在了runtime上,runtime是一个抽象概念,提供了运行时的实现,不需要开发人员操心,比如Java的runtime环境就是jvm。核心思想是模块化,通过sidecar的方式实现,然后通过本地rpc或者http调用。 1. Multi Runtime 分布式应用的需求: - 生命周...

Cloud Network

EIP分类

1. 全动态BGP(多线EIP) 云服务提供商的公网IP地址通过BGP与多个运营线直连的链路播报给多个运营商。BGP类型的带宽具备动态路由收敛能力,可靠性和抗DDos能力好,但价格相对静态BGP来说较贵。云服务提供商可以根据设定的寻路协议实时自动优化网络结构,以保持客户使用的网络持续稳定、高效。 2...

ER的作用

使用peering等方式构建的网络结构是Full Mesh,而企业路由器的网络结构是中心辐射型(星型拓扑)。

Aliyun LB介绍

1. ALB - 每个可用区占用三个ip,一个vip,两个local ip - 每个vip可以独立绑定eip - 。ALB实例的杭州可用区H发生故障时,ALB能够在短时间内停用该可用区,并继续使用其他启用的可用区提供服务。 - ALB默认开启跨AZ负载均衡,即ALB在同地域跨可用区的后端服务之间分配...

AWS LB介绍

应用程序负载均衡器(ALB)、网络负载均衡器(NLB)和网关负载均衡器(GLB)是云中使用的三类负载均衡器。 为了重定向应用程序流量 - ALB 会检查请求的内容,例如 HTTP 标头或 SSL 会话 ID - NLB 会检查 IP 地址和其他网络信息,以最佳方式重定向流量 - GLB 充当透明的网...

HW LB介绍

1. LB实例 - 可用区:选择部署的可用区列表,可用区越多,价格越贵 - 应用型和网络型:可组合选择购买应用型和网络型,组合不同,收费不同 - 网络配置: - 所属VPC - 网络类型:ipv4、ipv6,可组合选择,或都不选(只提供公网接入);选择ipv4后,可提供私网IP接入的私网负载均衡;选...

Nginx使用

1. nginx常用命令 Nginx的命令在控制台中输入就可以看到完整的命令,这里列举几个常用的命令: - nginx -s reload 向主进程发送信号,重新加载配置文件,热重启 - nginx -s reopen 重启 Nginx - nginx -s stop 快速关闭 - nginx -s...

Nginx架构与原理

1nginx默认的启动方式是多进程的方式,nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。worker的数量可以通过配置文件中的配置项控制,一般建议设置与机器cpu核数相当。 Nginx采用了事件驱动的模型,期望worke...

Aws

aws的nat网关作为vpc的一个特性,创NAT网关的时候可以选择公网还是私网。流量经过NAT网关,源地址都会转成网关的私网地址,公网NAT后面会经过internate gateway,然后将私网地址转换成公网地址。 1. 公网NAT 1.1. 性能 - 默认5 Gbps,最高100 Gbps - ...

AZURE

1. 公网NAT 只支持SNAT,不支持DNAT 1.1. 性能 - 50 Gbps - 100W - 5M PPS 1.2. other 1.2.1. 支持自定义TCP超时时间 1.2.2. 支持一组地址连续的公网地址池 支持16个EIP或/28大小的公共EIP前缀 1.2.3. 分布式VM提供服...

GCP

google的NAT网关比较特别,不是一个独立的网关设备,而是做在源计算节点上的源端NAT。Cloud NAT 是一种没有代管式中间代理的软件定义解决方案,采用,具有高可靠性、高性能和高可伸缩性。 1. 公网NAT Public NAT 网关会为使用网关创建与互联网的出站连接的每个虚拟机分配一组外部...

华为云

1. 竞争优势 不需要使用peering/er等实现跨vpc访问的能力,私网nat天然支持跨vpc网络访问。

腾讯云

1. 公网NAT 1.1. 性能 - 默认限速5 Gbps,最大可支持50 Gbps - 并发连接200万 - 10万新建 - 不占用vpc用户的私网ip 1.2. 配额 1.2.1. 网关 - 一个VPC支持3个NAT网关 - 每个NAT网关绑定最多10个EIP 1.2.2. SNAT - 单网关...

阿里云

1. 公网NAT 2. 特性 - 支持一键全通 - snat只允许修改eip - - 支持按量计费和带宽包 2.1.1. NIS(Network Intelligence Service) 支持实例级别的一键诊断,可以检测实例的配置与运行状态,并能根据诊断的异常项提供智能修复建议。诊断内容主要包括:...

Aws HyperPlane

- top完成所有包转发和包处理,一旦网络连接建立了,转发只在Top层完成 - Flow Master记录网络连接,充当Decider的缓存 - Decider实现网络逻辑,对于NAT来说就是进行会话分配 Flow Master可以主主扩展,利用率可以做得比较高,而Decider利用率相对来说较低。

Dpdk

Ovs

现在OpenVSwitch主要由三个部分组成: - ovsdb-server:OpenFlow本身被设计成网络数据包的一种处理流程,它没有考虑软件交换机的配置,例如配置QoS,关联SDN控制器等。ovsdb-server是OpenVSwitch对于OpenFlow实现的补充,它作为OpenVSwit...

发展历史

1. 转发平台发展 - 以openvswitch为代表的第一代内核态软件转发平台 - 以dpdk为代表的第二代内核态软件转发平台 - 以P4或NP为代表的第三代硬件转发平台。 第一代发展到第二代解决了内核态和用户态上下文切换代价较高的问题; 第二代发展到第三代解决了软件转发平台性能不足的问题。 第一...

云网络诞生

1. 产业竞争核心 - 芯片 - 操作系统 - 应用 2. 转发能力发展路线 虚拟交换机发展从10Gbit/s(以原始openvswitch为代表的内核态转发,使用 kernel 作为 datapath) -> 25Gbit/s(以dpdk技术为依托的内核态转发,使用用户空间作为 datapath)...

1. Vscode配置C++开发环境

1. 插件安装 C/C++ 2. 安装gcc 3. 配置C++环境 C++环境需要.vscode 文件夹下的以下三个文件共同定义 - ccppproperties.json :对C/C++扩展的设置。 - tasks.json :定义如何生成可执行文件。 - launch.json :定义如何调试可...

2. 基本语法

1. 程序入口 在 C++ 标准中, 函数是程序启动后调用的第一个用户定义函数,它有两种标准的定义形式: 1. :这种形式表示 函数不接受任何命令行参数。它向调用者返回一个整数值,用于表示程序的退出状态。通常,返回值 表示程序正常结束,非零值表示程序出现异常或错误。 2. :这种形式允许程序接收命令...

3. 命名空间

命名空间(namespace)是 C++ 中一项重要的特性,它可以将全局作用域划分为不同的部分,从而避免不同库或者不同模块之间的命名冲突。标准 C++ 库中的所有标识符(像类、函数、对象等)都被定义在了 命名空间里。 1. using namespace std 在没有使用 时,若要使用标准库中的标...

4. 变量定义与内存空间分配

C++ 中定义变量时,即使不进行初始化,内存空间通常也会被分配,不过不同类型的变量在分配内存空间的时机和默认初始值方面存在差异。 1. 局部变量(自动存储期) 局部变量一般定义在函数内部或者代码块内部,具有自动存储期。当程序执行到定义局部变量的语句时,会立即为其分配内存空间,但不会自动初始化,该内存...

5. String字符串

c++包含两种风格的字符串 - C 风格字符串 - C++ 引入的 string 类类型 1. char类型 C风格的字符串起源于C语言,并在C++中继续得到支持。字符串实际上是使用null字符\0终止的一维字符数组。因此,一个以null结尾的字符串,包含了组成字符串的字符。 2. 字符串函数 | ...

Code

Go Env

1. GOROOT与GOPATH - 环境变量表示 Go 语言的安装目录。在中,的默认值是,而在或中的默认值是,如果将Go安装在其他目录中,而需要将GOROOT的值修改为对应的目录。另外,则包含Go为我们提供的工具链,因此,应该将配置到环境变量 PATH 中,方便我们在全局中使用 Go 工具链。 -...

Go Mod

从 Go 1.11开始,Go引入了Go Modules作为官方的依赖管理解决方案。在Go Modules模式下,依赖项会被下载到目录下,命令默认会将可执行文件安装到目录下。文件位于项目根目录下,用于记录项目的模块信息和依赖项及其版本,一个典型的例子如下: 1. go mod命令 2. module ...

Go

Go命令行参数

- os.Args变量是一个字符串(string)的切片(slice) - os.Args的第一个元素,os.Args[0], 是命令本身的名字;其它的元素则是程序启动时传给它的参数

Channel

1. 声明和初始化 1.1. 仅声明(未初始化) 声明一个 int 类型的 channel,但未初始chnilpanicmake()make()ch := make(chan int)chch := make(chan int, 3)3varvar ch chan int = make(chan i...

Context

go1.7加入了一个新的标准库context,用来简化对于处理单个请求的多个goroutine之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个API调用。 fff88f">当一个上下文被取消时,它派生的所有上下文也被取消。当context生命周期结束时,可以从管道接收消息,子协...

Defer

1. 关键字 defer 用于注册延迟调用。 2. 这些调用直到return前才被执。因此,可以用来做资源清理。 3. 多个defer语句,按先进后出的方式执行。 4. defer语句中的变量,在defer声明时就决定了,语句中的fff88f">函数调用参数会在语句执行时立即求值,并保存下来。这意味...

Goto与Label

Go语言也支持label(标签)语法:分别是和 、。 - break label可以跳出label层级循环 - continue label可以从label继续下一次循环 - goto可以无条件的跳转执行的位置,但是不能跨函数,需要配合标签使用 执行结果 输出结果

函数

1. 结构体返回值 在Go语言中,函数返回结构体时,接收它的变量类型可以是值类型或指针类型 1.1. 使用值类型接收值类型返回值 - 结构体较小(如 2-3 个字段的结构体),直接复制不会有明显性能影响。 - 结构体的数据不需要在外部修改,返回值是独立的副本。 输出如下 1.2. 使用指针类型接收指...

变量赋值&结构访问

1. 取地址与解引用 是取地址符号,放到一个变量前使用就会返回相应变量的内存地址。如 是指针运算符,一个指针变量指向了一个值的内存地址。放到一个变量前可以对指针解引用,获取指针指向的实际变量。 输出如下 2. 变量赋值 go中直接对结构体进行复制,会进行值拷贝。 结构体的指针属性也会拷贝,但指针指向...

基本语法

基础

1. 变量初始化 go中使用var声明的变量会自动初始化,如下是等价的 2. array 1. 数组:是同一种数据类型的固定长度的序列。 2. 数组定义:,比如:,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。 3. 长度是数组类型的一部分,因此,和是不同的类型。 4. 数组可以通过...

并发

异常处理

在Go语言中,和都是用来处理错误情况的,但是它们的使用场景和应用场合是不同的。 1. panic 更适用于程序出现不可恢复的错误情况,例如违反了一些重要的前提条件、发生了一些严重错误等。一般来说,如果程序出现了,就意味着程序已经处于一个不可控的状态,无法继续执行下去。会导致程序立即终止并打印出错误堆...

循环与Select

1. select 语句会从上到下依次检查每个 的通信操作语句,每个case必须是一个通信操作,要么接收,要么发送。 - 如果发现某个 的通信操作可以立即执行,就会执行该 语句块并跳出 代码块。 - 如果多个 均可执行,则会随机选择一个执行。 - 如果没有任何一个 可以执行,则会执行 语句块(如果存...

类型断言

类型断言提供了访问接口值底层具体值的方式。 该语句断言接口值 保存了具体类型 ,并将其底层类型为 的值赋予变量 。若 并未保存 类型的值,该语句就会触发一个 panic。 为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。 t, ok := ...

结构体

常用Tool

1. golps golps 是LSP(Language Server Protocol)的一个语言端(Server)实现,是针对 Go 语言的LSP实现。定义了在编辑器或IDE中与语言服务器之间使用的协议,该语言服务器提供诸如自动完成,转到定义,查找所有引用等语言功能。语言服务器索引格式(LSIF...

Kafka

Redis

1. simple example 2. redis pool

Web

测试

Golang单元测试对文件名和方法名,参数都有很严格的要求。 1. 文件名必须以xxtest.go命名 2. 方法参数必须 3. 使用go test执行单元测试 在文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。 | 类型 | 格式 | 作用 | | ---- | ----------...

Benchmark

Go语言标准库内置的testing测试框架提供了基准测试(benchmark)的能力,能让我们很容易地对某一段代码进行性能测试。 本文中会对斐波那契数列进行基准测试 创建一个基准测试用例 - Benchmark测试文件必须以filename加结尾 - 方法名必须以开头 - 测试参数必须为 1. 执行...

Go性能分析

golang中性能调试优化的方法包括: - benchmark:基准测试,对特定代码的运行时间和内存信息等进行测试 - profiling: 程序分析,程序的运行画像,在程序执行期间,通过采样收集的数据对程序进行分析 - Trace:跟踪,在程序执行期间,通过采集发生的事件数据对程序进行分析 > [...

Pprof

1. 使用方式 必须在代码里引入才能使用,不像Java里jdk工具包中的 、、、、 工具可以单独使用。可以从以下两个包中引入 Golang pprof的使用方式主要有两种 1. 在程序中通过http接口的方式暴露相应的pprof的采集控制界面,需要依赖, 使用 包来进行封装。 2. 可以用来产生du...

程序调优

进程、线程与协程

1. go中线程的数量 Go 使用Goroutine 调度器 (Scheduler) 来管理Goroutine的执行。调度器的核心概念如下 1.1. GMP模型 goalng采用特有的GMP模型。 1. G(Goroutine):指的是 Go 代码中的 Goroutine。 2. M(machine...

Go面向对象

1. 自定义数据类型 增强代码可读性 2. 方法接收器 只有自定义类型(或内置类型)才能够绑定方法,从而获得面向对象的特性。例如,为 绑定 方法。,使得User对象实现了Name方法。 在Go语言中,接口的实现是隐式的,也就是说只要一个自定义类型实现了接口中声明的所有方法,那么它就被认为实现了该接口...

反射

1. 任意类型 在 Go 语言中, 常被称为“空接口”,它的确能承载fff88f">任意类型的值,但这种“万能”特性背后存在明显的权衡和局限性。也可以用关键词替代, go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息 对于这个例子, 的类型信息是 ,数据信息是 ,这两部分信息都是存储...

泛型

1. 泛型 我们知道,函数的 形参(parameter) 只是类似占位符的东西并没有具体的值,只有我们调用函数传入实参(argument) 之后才有具体的值。那么,如果我们将形参 实参这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,问题就迎刃而解:在这里我们将其称之为 类型形参(typ...

Http连接池

1. 长连接 长连接利用keep-alive技术实现,能在多次 HTTP 之间重用同一个 TCP 连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等)。 然而长连接并非没有弊端,天下没有免费的午餐,如果客户端在接收完所有的信息之后还没有关闭连接,则服务端相应的资...

Java

Java9 Java17新特性

1. 集合 增加 了 List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。 2. 私有方法 java8中允许接口有默认方法,java9中允许接口有默认私有方法 3. Optioal的ifPresentOrElse 4. 类型推断 类型...

Jni

会在指定的所有目录下查找名为 libnativememoryutils.so(Linux)或 nativememoryutils.dll(Windows)的文件。 > [!note] > 在 Linux 下,文件名必须是 libnativememoryutils.so,不能省略 lib 前缀。 1....

Mtls实现

1. 生成证书和密钥库 1.1. openssl配置 openssl中可以通过--config指定完整的配置文件,包含所有配置段(sections),用于生成包含扩展字段的证书签名请求(CSR)或自签名证书。 1.2. 生成客户端证书和服务端证书 2. 本地安装ca证书和客户端证书 windows可...

Velocity

1. 新建module-a 引入maven依赖 自定义Annotation 继承AbstractProcessor,实现自定义Processor 2. 新建module-b 添加依赖module-A 类填写注解,以生成编译后的class。可以添加些属性,用于编译class时使用。也可以给多个类添加注...

内存管理

1. 总结 java应用的内存主要分为三块 1. jvm管理的堆内存 2. jvm管理的非堆内存 3. 非jvm管理的内存 1.1. JVM管理的堆内存(Heap Memory) 存储对象实例,是 Java 内存管理的核心区域,由所有线程共享。 - 新生代(Young Generation):存放新...

虚拟线程

1. 概念 1.1. 平台线程 我们常用的Java线程与系统内核线程是一一对应的,系统内核的线程调度程序负责调度 Java线程。为了增加应用程序的性能,我们会增加越来越多的Java线程,而由于多种因素,平台线程的数量受到了很大的约束。 - 资源有限导致系统线程总量有限,进而导致与系统线程一一对应的平...

Grpc

1. 工具推荐 - apifox:rpc客户端,可以导入proto文件,自动生成rpc客户端并重试 2. 通信过程 3. 抓包 4. protocol buffers Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数...

Rpc

4+1视图

| 视图类型 | 描述 | | :-------------- | :------------------------------------------------------------------------------------------ | | 逻辑视图 | 逻辑视图面向系统逻辑分析和...

DDD

DDD理论与实践

1. 什么是DDD DDD是一种处理高度复杂领域的ff0000">设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD不是架构,而是一种架构ff0000">设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域...

核心概念

通用模型 NAT网关模型 1. 领域 汉语词典的解释:领域是从事一种专门活动或事业的范围 、部类或部门。 百度百科的解释:领域具体指一种特定的范围或区域。 两个解释有一个共同点——范围,领域的核心重点落在ff0000">”域”字上,用来确定范围的,fff88f">范围即边界 ,这也是DDD在设计中不...

Design

代码坏味道

1. 重复代码 2. 长函数 3. large class 4. Divergent Change(发散式变化) 对程序进行维护时, 如果添加修改组件, 要同时修改一个类中的多个方法,那么这就是 Divergent Change。举个汽车的例子,某个汽车厂商生产三种品牌的汽车:BMW、Benz和La...

设计原则与典型架构设计模型

架构的设计本质都是为了高内聚、低耦合 1. SOLID原则 1.1. S单一职责原则(single responsibility) 一个class应该只做一件事,一个class应该只有一个变化的原因,核心是ff0000">功能特性解耦和ff0000">高内聚性。避免一个类承担两个特性,修改A特性的时...

1. 为什么要Dpdk(Data Plane Development Kit)

1. 基于OS内核转发的劣势 1.1. 数据路径长,协议栈处理开销大 - 内核路径: 数据包需经过内核协议栈(如TCP/IP栈)的多层处理(链路层→网络层→传输层→应用层),涉及大量内存拷贝、校验和计算、上下文切换等操作。 - DPDK优化: 直接绕过内核,在用户空间处理数据包,省去协议栈的逐层解析...

ConfigMap

1. 创建 1.1. 通过kubectl命令行创建 1.1.1. --from-file参数从文件中进行创建 其中key=是可选的,默认key就是文件名,通过key=可以指定key。 1.1.2. --from-file参数从目录中进行创建 目录下每个配置文件名都被设置为key,文件的内容设成为va...

Ingress Gateway

在Kubernetes的服务网格架构中,Ingress Gateway是通过Envoy实现的。在Istio中,Ingress Gateway 是用于管理进入服务网格的流量的组件,它基于 Envoy 代理构建。Envoy 是一个高性能的代理,专门设计用于处理服务到服务的通信,支持动态服务发现、负载均衡...

Kube Proxy和Istio Envoy

- 每个节点安装了一个kube-proxy - 每个pod以sidecar的形式部署一个envoy - kube-proxy拦截的是进出kubernetes节点的流量,envoy拦截的是进出pod的流量。 - istio作为服务网格的一种实现,本质上提供了应用间的流量、安全管理和可观察性。 1. k...

Kubernetes 核心概念

kubernetes中的基本概念和术语大多是围绕资源对象来说的,而资源对象在总体上可以分为以下两类: (1)某种资源的对象,例如节点(Node),Pod,服务(Service),存储卷(Volume) (2)与资源对象相关的事物与动作,例如标签(Label)、注解(Annotation)、命名空间(...

Pod调度

k8s Master上的Scheduler服务负责实现Pod调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通过我们无法知道Pod最终会被调度到哪个节点上。 1. NodeSelector 可以实现将Pod调度到一些指定的Node上,可以...

Service

一个service对外暴露一个,client可以访问这个来实现负载均衡访问后端Pod。一个service对应的“后端”由pod的ip和端口号组成,这在kubernetes中称为endpoint。一组endpoints形如,其中和都是podIp。

Postgresql DML

1. 常用运维sql

性能优化

1. 查看数据库CPU有没有冲高,一般是有慢SQL 2. 查看数据库I/O有没有冲高,如果有一般是用了外部排序 3. 优化外部排序有两个思路,一个是添加索引,避免临时排序;第二个是限制数据规模,尽量保证在内存中完成排序 4. 先联表后排序,索引会无法利用;要做利用索引做子查询排序,然后再联表 5. ...

数据库关键配置

1. socketTimeout 未配置socketTimeout时,应用不会超时,会永远等待。当出现网络隔离故障时,服务端访问数据库的连接会hang住,导致需要等到tcp重传失败或者keepalive失败才会断开连接,故障时间会非常长。 数据库连接池中的连接可能处于如下两种状态 - 连接处于繁忙状...

数据库理论

1. 三大范式 - 第一范式(1NF) 保证列的原子性,一张表的某个列不能有多个值,不能出现重复列。实际操作中我们可能会违反这个约束,如将列值存成一个json字典,但是我们一定要保证这些值至少是一起读或写的,不存在经常部分更新的场景。 - 第二范式(2NF): 取消部分依赖,表中的每个字段都与主键相...

Etcd

Etcd常见用法

1. 数据put和get 1.1. 设置key和value 1.2. 获取指定key 1.3. 获取前缀下所有key 2. 数据过期与续约 2.1. 创建租约并设置 TTL(Time To Live) 创建一个 TTL 为 60 秒的租约: 该命令会返回一个租约ID,例如 2.2. 查看所有leas...

Etcd架构

etcd 集群通过Raft算法实现了 “动态主从 + 分布式共识” 的架构,确保数据强一致性和高可用性。etcd 集群在同一时刻只能有一个主节点(Leader),且写操作必须由该主节点处理,但读操作可以从任意节点执行。etcd集群中的节点在正常运行时通过Raft算法动态选举 “领导者(Leader)...

Watch使用与原理

Watch机制是etcd的核心功能之一,它允许客户端监听指定键或前缀的变化,实现配置变更的实时通知。默认情况下,watch是从ff0000">最新版本开始监听的。 1. 使用方式 1.1. 基本使用 添加或修改key etcd提供了多种方式使用Watch功能 1.1.1. 命令行方式 1.1.2. ...

数据存储

1. 数据存储格式 etcd的数据存储格式主要是基于键值对的形式。 - 键:是一个唯一的字符串,用于标识存储的数据,类似于字典中的键。例如,在一个配置管理系统中,键可能是某个服务的名称加上配置项的名称,如。 - 值:可以是任意类型的数据,如字符串、整数、JSON 对象等,这取决于具体的应用场景。例如...

Kafka Stream

1. 流式计算和批计算 1.1. 流式计算 流式计算是持续地从数据源获取数据,并实时地对数据进行处理和分析。 1.1.1. 特点 - 实时性强,能够在数据到达时立即进行处理。 - 数据无边界,是持续产生的 - 计算模型有状态,需要维护中间状态,一般用增量计算代替全量计算 - 时延敏感,需要低延迟处理...

Kafka架构

1. topic kafka将消息以topic为单位进行分类,一个topic就是一个逻辑队列。类比于数据库中的分库。 2. partition 为了实现扩展性,提高并发能力,kafka将一个逻辑队列(topic)划分为多个partition,每个partition保存一个topic中的部分数据,每一...

Kafka核心配置项

这里我们以网络服务用的最多的kafka组件为例,来介绍下其实现原理,已经如何在知晓原理的情况下用好kafka。 1. producer - : 控制producer端开启幂等和事务能力。开启幂等后能够保证消息不重复。 - 为生产者提供了一个唯一的事务标识符。通过这个标识符,Kafka 能够跟踪和管理...

Kafka生产消息

1. kafka获取partition对应节点 通过元数据获取 - Kafka producer 在启动时会向 Kafka 集群中的任意一个 broker(通常是配置中的 bootstrap.servers 列表中的一个)发送元数据请求(Metadata Request)。这个请求用于获取集群的元数...

Kafka高可靠

1. 生产可靠 为保证Producer发送的数据,能可靠地发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送ACK(ACKnowledge 确认收到)。如果Producer收到ACK,就会进行下一轮的发送,否则重新发送数据。 1....

Kafka高性能读写

核心: - 顺序读写 - page cache,避免每次读取磁盘。也正是因为顺序读写,避免了大量page cache miss,可以充分利用page cache。 - 零拷贝。 1. 存储结构 一个topic下多个partition,每个partition使用一个文件夹存储。partition命名规...

不同消息队列对比

- RocketMQ 的存储结构相对较为灵活,可以针对不同的场景进行优化。它采用了单一的 commitlog 文件来顺序存储所有消息,然后通过索引文件快速定位特定topic的消息。这种设计使得在处理大量 topic 时,存储资源的分配更加高效。每个topic都对应独立索引文件,加上page cach...

云时代的消息中间件

消息队列核心

- 为什么需要消息队列 - 消息队列的核心设计和使用 - 深入消息队列高性能原理 cap原则 1. 为什么需用消息队列 1.1. 业务解耦 业务解耦,代码解耦。现在的云网络服务大都遵循SDN设计思想,将服务拆分成管控转三部分,不同组件负责承载不同的业务,其解耦方式之一就是通过消息队列。很多消息队列都...

Middleware

Redis

Redis关键参数

1. go-redis 在go- redis中,连接设置是通过 Options 结构体来管理的。 1.1. 单机模式 - Network - 网络类型,可以是 tcp 或 unix,默认值为 tcp。 - Addr - Redis 服务器的 host:port 地址。 - Dialer - 创建新网...

Redis命令

1. redis支持数据类型 - 字符串 - hash(key-value) - list(有序列表) - set(无序唯一集合) - zset(有序唯一优先级队列,每个元素关联一个分数,按从小到大排列) - Bitmap - HyperLogLog,占用内存很小(12kb)的情况,可以用于估算接近...

Redis批处理

1. pipeline Redis执行一条命令需要经历以下过程:、、、。由于Redis本身是基于协议(停等机制)的,虽然Redis已经提供了像 、 这种批量的命令,但是如果某些操作根本就不支持或没有批量的操作或者需要连续执行好几个不同命令,那我们就只能一条一条地执行命令,每执行一条命令都要消耗请求与...

Redis高可用

1. master和slave数据复制机制 在Redis集群模式下,Master和Slave之间默认采用异步复制 - 客户端向Master写入数据后,Master立即返回成功响应,无需等待Slave确认。 - 数据随后异步复制到 Slave 节点。 这种设计的目的是保证高性能和可用性,但存在数据丢失...

代码示例

发布订阅模式

Redis发布订阅简介 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis的SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。 作为例子, 下...

Zookeeper

分布式锁

1. 分布式公平锁 ZooKeeper的临时顺序节点,天生就有一副实现分布式锁的胚子。 1. ZooKeeper的每一个节点,都是一个天然的顺序发号器。 在每一个节点下面创建临时顺序节点(EPHEMERAL\SEQUENTIAL)类型,新的子节点后面,会加上一个次序编号,而这个生成的次序编号,是上一...

基本概念

1. zookeeper数据存储 ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如 /hbase/master,其中 hbase 和 master 都是 ZNode。每个 ZNode 上都会保存自己的数据内容,同时...

DCN

DCN架构演进

1. 服务与网络三大耦合问题 - 交换机堆叠,存在单点风险和运维困难问题 - 物理网络大二层隧道,引入更大故障域 - 硬件墙的引入限制整体云业务规模,硬件强性能不行,无法横向扩展 2. 名词解释 NSA(Network Service Area) - NWS(Network Service): 网络...

交换机堆叠

1. 背景 主机连接单个交换机,存在单点故障问题(单个交换机故障),解决tor交换机单点故障的思路有两个。 - 上层服务(计算、网络)保证高可用,当一个tor下主机故障时,可以快速在另一台tor下拉起新主机 - 两台tor交换机堆叠,保证tor交换机的高可靠 2. 概念 交换机堆叠一般是指被背板堆叠...

大二层架构的问题

1. 历史背景 网关在汇聚上的原因是因为openstack平面约束,要求资源池里面的节点必须在一个二层上。 管理服务区解大二层原因是管理区容灾1.0方案依赖vip在az间漂移,管理区汇聚下就是一个二层。同时,az之间还需要在两个az的汇聚交换机上通过硬件vxlan打一个二层隧道。 2. 大二层的问题...

(七)Linux下实现NAT

在NAT Overview一文中,我们介绍了在linux下通过iptables实现NAT功能的命令及原理。本文以容器为例,进行几个简单的实验来进一步了解linux下NAT的实现过程和应用。 1. nat在容器中的应用 1.1. SNAT linux下一个最典型的NAT应用就是docker容器借助宿主...

(九)高性能NAT

(五)NAT ALG

--- title: NAT ALG date: 2023-02-01 categories: - network tags: - network - nat --- 能够对特定的应用层协议进行转换,在对这些特定的应用层协议进行转换过程中,通过的会话信息来改变封装在报文载荷中的和端口信息,最终实现下...

(八)高可靠NAT

1. 双机冷备 2. 双机热备 双机热备可以极大地提升NAT服务的可靠性和承载能力,但是两台NAT设备上的NAT配置也需要完全相同。这样就会出现一个问题:如果两个NAT设备分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了优先级的概念。 在双机热备的环境...

(六)路由器配置NAT

本章节主要以cisco路由器配置为例,使用GNS3仿真平台搭建一个最简单的实验环境进行验证。 1. 常用命令(cisco) 首先介绍本实验可能用到的一些命令,先有个概念,后面再在实验中熟悉并掌握。 1.1. VPC 1.2. 路由器 2. 环境 2.1. 配置PC 2.1.1. PC1 2.1.2....

(十)NAT穿透

NAT高可靠

1. 双机热备 双机热备对于防火墙来说是一个必不可少的功能,两台防火墙上的NAT配置也需要完全相同,这样就会出现一个问题:如果两个防火墙分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了地址池优先级的概念。 在双机热备的环境中,如果地址池被配置为高优先级...

流日志

1. 格式 1.1. 网络信息7元组 internalip,internalport,externalip,externalport,transitip,transitport,protocol 1.2. 流量数据 packetnum,bytesize 1.3. 时间信息 starttime,end...

Network

IP分片报文和TCP分段报文

分组可以发生在传输层和网络层,传输层中的TCP会分段,网络层中的IP会分片。IP层的分片更多的是为传输层的UDP服务的,由于TCP自己会避免IP的分片,所以使用TCP传输在IP层都不会发生分片的现象。我们在学习TCP/IP协议时都知道,TCP报文段如果很长的话,会在发送时发生分段,在接受时进行重组,...

SCTP

SCTP ( Stream Control Transmission Protocol ),即流媒体控制传输协议,是一种可靠的基于无连接数据包网络如IP网络之上传输协议。他被设计用来在IP网络上传输PSTN在窄带信令消息,同时也能支持宽带信令消息的传输。 SCTP可以看作OSI层次结构中的传输层,它...

Tcp KeepAlive

1. 起源 连接有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接。长连接环境下,双方建立交互的连接并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现: 1. 主机突然掉电、死机、异常重启 2. 中间路由...

Tcp重传

1. tcp超时重传 重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的确认应答报文,就会重发该数据,也就是我们常说的超时重传。 TCP 会在以下两种情况发生超时重传: - 数据包丢失 - 确认应答丢失 1.1. RTT与RTO - RTT(Roud Tri...

Nat封装Pp协议

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements client ^pdsoIL0z NAT ^lEtOOR5s Server ^7HyKnicB SYN seq0 ^3...

源地址透传

1. TOA(TCP Option Address) TOA将源地址放在字段中。option字段最长40字节,每个选项由三部分组成:op-kind、op-length、op-data,我们最常见的MSS字段就是在option里。目前option使用的op-kind并不多,我们只需要构建一个不冲突的o...

DHCP协议

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,统一使用两个IANA分配的端口:67(服务器端),68(客户端),DHCP客户端使用的源端口号为68,目的端口号为67发送请求消息到...

Dns Zone

DNS Zone是指域名系统(DNS)中管理的一部分域空间,包含了该区域内所有域名的记录信息。它是确保网络中计算机能够通过域名解析到正确 IP 地址的关键组成部分,对于维持互联网的正常运行至关重要。一个DNS Zone通常由一个组织或个人负责维护,这个区域包括了域名及其子域的权威信息,如IP地址、邮...

DNS

DNS配置与匹配规则

1. multi dns ip 对于一个主机配置多个DNS IP 1.1. 作用 1.1.1. 冗余和容错 - 当第一个DNS服务器无法响应时,主机会自动尝试使用第二个、第三个DNS IP - 提高DNS解析的可靠性,即使一个DNS服务器宕机,仍可通过其他DNS服务器进行域名解析 1.1.2. 负载...

Https抓包

由于主流浏览器都只支持HTTP/2 Over TLS,也就是说当前HTTP/2网站都使用了HTTPS,数据传输都经过了SSL加密,常规抓包方法并不能看到明文数据。 1. wireshark Wireshark 的抓包原理是直接读取并分析网卡数据,要想让它解密 HTTPS 流量,有两个办法: 1)如果...

HTTP协议解析

1. HTTP 1.0 !http1.0抓包.pcapng HTTP/1.0 默认为每一对 HTTP 请求/响应都打开一个单独的 TCP 连接。 - 17945-17947:tcp三次握手 - 17948:Server告诉Client更新自己的接收窗口大小 - 17949:Client发起HTTP ...

TLS

1. TLS是什么 Transport Layer Security (TLS) 是一种被广泛采用的安全协议,旨在增强互联网通信的私密性和数据安全性。TLS的主要使用场景是对Web应用和服务器之间的通信(例如,Web 浏览器加载网站)进行加密。TLS 还可以用于加密其他通信,如电子邮件、消息传递和 ...

常用Http Header

1. remoteaddr 表示发出请求的远程主机的IP地址,remoteaddr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时 1. 假设中间没有任何代理,那么网站的web(,Apache等)就会把remoteaddr设为你的机器IP 2...

应用层

ARP

1. arp响应的条件 1.1. 普通主机响应 ARP 请求的条件 当本机是普通主机时,只有当 ARP 请求中的目标IP地址与本机IP地址匹配时,普通主机才会发送ARP响应。例如,主机A的IP地址为192.168.1.10,当它接收到ARP请求,且请求的目标 IP地址为192.168.1.10时,主...

VLAN

1. 介绍 当网络上所有设备在同一个广播域产生大量的广播和多播帧(arp/dhcp/stp/rip),就会与业务数据流争带宽,造成网络性能恶化。将大型广播域分段是提高网络性能的方法之一。路由器能够将广播包阻隔在一个三层接口上,但是路由器的LAN接口数量有限,它的主要功能是在三层网络间传输数据,而不是...

交换机原理

1. SAT(MAC地址表、FDB、CAM) 交换机的Source Address Table(源地址表),也称为MAC地址表、FDB(forwarding database)或CAM(Content Addressable Memory),是一种用于存储和管理设备MAC地址和端口之间映射关系的表格...

概念解释

ISP: Internet Service Provider 互联网服务提供商 IXP: Internet eXchange Point 互联网交换点 通信方式: - 客户-服务器方式(C/S) - 对等方式(P2P) 1. 电路交换 电路交换必须经过“建立连接(占用 通信资源)一通话(一直占用通信...

IPSec

IPSec (Internet Protocol Security) 协议主要工作在OSI模型的第3层网络层,为上层传输层协议(如TCP、UDP等)提供安全支持。IPSec不是一个单独网络协议,而是一系列为IP网络提供安全性的协议和服务的集合,提供了认证、加密、数据完整性等安全服务。 1. 主要协议...

仿真

Ipv4地址

1. 特殊ip地址汇总 - 0.0.0.0/8:用于广播信息到当前主机 - 10.0.0.0/8:用于专用网络中的本地通信 - 172.16.0.0/12:用于专用网络中的本地通信 - 192.168.0.0/16:用于专用网络中的本地通信 - 100.64.0.0/10:用于在电信级NAT环境中服...

IPv6地址

1. 地址格式 IPv6地址有128bit,在这128bit中,前64bit是网络前缀,后64bit是接口标识。在前64bit中,前48bit又是全球可汇总地址,在给一个公司分配IPv6地址时,总是分配给它一个前48bit固定的地址,而后面的16bit又可以被该公司用来做子网地址。这样分配,可以方便...

网络层

链路本地地址

链路本地地址(Link-local Address)是一类特殊的IP地址,仅用于在网段内、同一广播域内的主机相互通信使用,这类主机可认为是不需要外部互联网服务的。其中IPv4的链路本地地址定义在169.254.0.0/16范围内,IPv6定义在fe80::/10范围。链路本地地址在ipv4和ipv6...

BGP

BGP基础概念

1. AS - OSPF、IS-IS等IGP路由协议在组织机构网络内部广泛应用,随着网络规模扩大,网络中路由数量不断增长,IGP已无法管理大规模网络,AS的概念由此诞生。 - AS指的是在同一个组织管理下,使用统一选路策略的设备集合。 > [!question] 不同的AS之间需要进行通信,在AS之...

BGP邻居建立

- 先启动BGP的一端先发起TCP连接,R1先启动BGP,R1使用随机端口号向R2的179端口发起TCP连接,完成TCP连接的建立。 - 三次握手建立完成之后,R1、R2之间相互发送Open报文,携带参数用于对等体建立 - 参数协商正常之后双方相互发送Keepalive报文,收到对端发送的Keepa...

路由协议

CPU

1. 基本概念 - Socket: 指的是主板上用于安装物理CPU芯片的接口。每一个这样的接口能够插入一个物理的CPU处理器,一台计算机可以配备多个这样的插槽,进而安装多个物理CPU,以增强系统的计算能力和性能。一个物理CPU可以有多个物理CPU核。 - 物理核(): 可以看的到的,真实的cpu核,...

File System

常见文件目录

各大发行版基本一致,遵循的是通用的FHS(Filesystem Hierarchy Standard)约定,(个别子目录如 lib64、multiarch 名称可能略有不同) 1. 开发者部署模板 用一个假想应用myapp为例,展示其在Linux系统中应该如何分布文件和目录。 🏗️ myapp 部...

Linux

Bridge

1. 常见命令 2. 泛洪 - 泛洪机制:桥接设备会将该数据包从除接收端口之外的所有其他端口发送出去。这样做的目的是确保目的设备无论连接到哪个端口,都有机会接收到这个数据包。例如,在一个包含多个主机的局域网中,如果主机 A 向一个桥接设备发送了一个数据包,而该桥接设备的 FDB 表中没有目的主机的 ...

Geneve隧道配置

1. geneve协议格式 GENEVE与VXLAN类似,仍然是Ethernet over UDP,也就是用UDP封装Ethernet。VXLAN header是固定长度的(8个字节,其中包含24bit VNI),与VXLAN不同的是,GENEVE header中增加了TLV(Type-Length...

Network Namespace基础

可以创建一个完全隔离的新网络环境,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables等等。总之,与网络有关的组件都是独立的。 可以看到我们刚才创建的网络环境 进入虚拟网络环境,使用命令 只能看到lo口 这样我们可以在新的网络环境中打开一个shell。 连...

Route Table与VRF

在 Linux 系统中,支持使用多个 route table(路由表) 是为了增强网络配置的灵活性和功能性。多路由表的主要作用如下: 1. 实现策略路由(Policy Routing) 默认情况下,Linux 使用主路由表()中的路由规则来决定流量的转发路径。然而,某些场景下可能需要基于流量的源地址...

Tcp关键内核参数

1. tcpsynretries 这个参数值设置的是client发送SYN如果server端不回复的话,ff0000">重传SYN的次数。对我们的直接影响就是connet建立连接时的超时时间。当然Java通过一些C原生系统调用的组合使得我们可以进行超时时间的设置。在Linux里面默认设置是5,下面给...

Tun Tap介绍

在计算机网络中,TUN(Tunnel)与TAP(Test Access Point)是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。 - TAP等同于一个以太网设备,它操作第二层数据包...

Vxlan隧道配置

1. 主机应用通过绑定独立路由表或vrf的方式通过隧道访问对端 > 图为网图,本文配置vtep地址换成了10.0.0.250和10.0.0.251 250主机配置 查看当前系统信息 > 虽然从概念上讲,VRF 主要用于实现逻辑上的路由隔离,但在系统的网络配置和管理中,它被呈现为一个类似普通网络接口的...

管道

1. client 2. server 3. ouput 数据直接在内存中的管道缓冲区传输

网卡混杂

网卡在混杂模式下不会校验收到的报文的mac和ip

CPU使用率

1. 查看进程CPU使用率 1.1. 平均使用率 ps命令显示的值是进程的平均CPU使用率,计算公式为: $\%CPU=(进程使用的CPU时间​/进程运行的总时间)×100$ 如下命令显示CPU占用最高的前几名进程 输出如下 1.2. 瞬时使用率 如果想监控某个进程(比如 )的 CPU 使用率 ...

Ls

1. 基于文件名排序 2. 基于文件大小排序 3. 基于文件时间排序

Lsof

(List Open Files)是一个用于列出系统中打开文件的工具。这里的 “文件” 是一个广义的概念,包括常规文件、目录、块设备、字符设备、共享库、网络套接字等。会列出系统中当前用户打开的所有文件。 : 列出指定进程 ID 的进程打开的文件 :列出指定用户打开的文件 : 列出正在执行指定命令的进...

PS

命令是process status的简称,用于显示当前运行的进程的信息。在不使用任何标识的情况下,会显示所有当前用户启动的进程,比如: - PID: 进程的ID号 - TTY: 终端名称缩写 - TIME: CPU时间,即进程使用CPU的总时间 - CMD: 所执行的命令名称。 1. 参数 命令支持...

Sort

-f :忽略大小写的差异,例如 A 与 a 视为编码相同; -b :忽略最前面的空格符部分; -M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法; -n :使用『纯数字』进行排序(默认是以文字型态来排序的); -r :反向排序; -u :就是 uniq ,去重,相同的数据中,仅出现一...

常用运维命令

核隔离和Cgroup

!CPU1. 基本概念 1. 核隔离 修改内核启动参数 1.1. CPU加压测试 htop可以看到隔离的核没有受到影响 2. 绑定进程到隔离核心 3. cgroup 输出如下 > [!info] > Worker 0 started on CPU 2 Worker 2 started on CPU ...

Numa架构

是一种多处理器架构,它将系统中的内存分成多个节点,并将每个节点分配给不同的处理器。在 NUMA 架构中,每个处理器可以访问本地节点的内存,但访问远程节点的内存速度较慢。因此,NUMA 架构可以提高多处理器系统的性能和可扩展性。 下图为英特尔S2600系列服务器主板 - 两个CPU插槽,CPU插槽之间...

Os

Page Cache

文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被CPU访问。由于读写硬盘的速度比读写内存要慢很多,所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux内核使用页缓存(Page Cache)机制来对文件中的数据进...

中断

1. 中断(Interrupt)的定义 中断是计算机系统中一种关键机制,允许处理器暂停当前执行的任务,转去处理更高优先级的紧急事件,处理完成后恢复原任务。中断机制提高了系统的响应速度和资源利用率,是操作系统实现多任务、实时响应的基础。 2. 硬件中断(Hardware Interrupt) vs 软...

操作系统地址空间

操作系统的地址空间是一个抽象概念,表示操作系统在运行时能够管理和访问的内存范围。它提供了一种将物理内存映射到逻辑地址的机制,使得程序能够在一个独立于物理硬件的虚拟地址空间中执行。 1. 用户空间与内核空间 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所...

操作系统概述

1. 操作系统基本特征 1.1. 并发 并发是指两个或多个事件在同一时间间隔内发生,在多道程序环境下,一段时间内宏观上有多个程序在同时执行,而在同一时刻,单处理器环境下实际上只有一个程序在执行,故微观上这些程序还是在分时的交替进行。操作系统的并发是通过分时得以实现的。操作系统的并发性是指计算机系统中...

进程调度策略

进程调度的关注点: - 性能:执行完所有任务需要的总时间。 - 公平性:每个任务都期望自己先被执行,尽早完成。 - 响应时间:首次运行时间 - 任务提交时间。 性能、公平性、响应时间是矛盾的,为了达到最好的性能,我们希望cpu不要在不同进程或线程之间切换,这样避免了上下文的切换,一个任务从开始执行到...

Openssl

1. RSA密钥 生成私钥 - :指定算法为 RSA - :指定密钥长度为 2048 位(可改为 4096 - :输出私钥文件路径 生成公钥 - :输入私钥 - :导出公钥 - :输出公钥文件路径 2. ECC密钥 生成对应的ECC私钥 - :指定椭圆曲线(可换成 , , , 等) - :生成密钥 ...

SSL证书

在密码学和数字证书体系中,一般没有“私钥证书”的说法。主要是公钥证书。公钥证书(通常就简称为数字证书)是一种电子文档,包含以下信息 - 公钥信息 - 持有者身份信息(如姓名、组织等) - 证书颁发机构(CA)的数字签名等 其主要目的是将公钥与特定的实体(如个人、服务器等)绑定,用于在网络通信等场景中...

Webauthn

在数字时代,密码已成为我们日常生活和在线活动中不可或缺的一部分。尽管互联网已经发展了20多年,许多方面都有了巨大的改进,但只有密码,还是20年前的用法。更准确的说,它的用户体验比 20 年前更差了。 1. 密码的强度现在要求越来越高,一般不能少于8个字符,还要包括特殊符号。 2. 除了密码,通常还有...

Tech

CPU架构

1. 主频 CPU 主频,即CPU的时钟频率,是衡量 CPU 运算速度的重要指标,以下从其定义、工作原理及与性能的关系等方面进行具体解释。 - 基本定义:CPU 主频表示 CPU 内数字脉冲信号振荡的速度,单位为赫兹(Hz)。通常所说的CPU 主频为2.4GHz、3.6GHz等,意味着CPU内部的时...

CPU缓存设计

1. 缓存 Cache Memory也被称为Cache,是存储器子系统的组成部分,存放着程序经常使用的指令和数据,这就是Cache的传统定义。从广义的角度上看,Cache是快设备为了缓解访问慢设备延时的预留的Buffer,从而可以在掩盖访问延时的同时,尽可能地提高数据传输率。 快和慢是一个相对概念,...

CPU调度策略

1. MLFQ(Multi-Level Feedback Queue) - 系统中存在多个就绪队列,每个队列有不同的优先级,高优先级队列中的进程会优先获得 CPU 执行。 - 进程在队列之间可以移动,根据其行为(如 CPU 占用时间、I/O 操作等)动态调整优先级。 1.1. 规则 1.1.1. 优...

Blog Online

TODO

- BGP实践,BGP PEER建立 - 核隔离与CGROUP - NUMA - 中断 - 硬件卸载 - 大页 - etcd 1. cloud wan ccn一个vpc支持创建几个attachment ccn路由同步,会同步到未关联的attachment吗?默认会同步所有路由表吗?

Icloud

使用土区账号购买50G icloud,平均每个月2.5块 1. 土区账号登录icloud 2. 国区账号登录apple store 3. 闲鱼购买土区礼品卡充值到土区账号 4. 土区账号购买icloud 5. 过去账号通过airdop发送邀请土区账号加入家庭组 6. 土区账号共享icloud设置家庭...

Excel

1. 将多个sheet join合并 Power Query自动化合并(适合多列/大数据) 1. 导入数据到Power Query: - 在Excel中点击数据 > Power Query > 获取数据,加载Sheet1和Sheet2。 2. 合并查询: - 选择Sheet1作为主表,与Sheet2...

Vnc远程桌面

总览

Clash For Linux

Jupyter安装

z从sqlite官网,下载安装最新版本sqlite3 清理旧版本的sqlite3 安装并编译python环境 配置jupyter lab 添加python环境 1. 格式转换

Movie Pilot

1. docker 安装 注意要通过环境变量配置代理,否则无法访问themoviedb或thetvdb等 2. 下载与整理 可以通过软连接目录的方式达到下载目录和mp的/media目录一致。 3. ref https://wiki.movie-pilot.org/zh/install

Movie Bot

NAS搭建

1. 主机硬件 - 主板:微星B360M Mortar - CPU:i3-8300t - 内存:金士顿骇客8G 2 DDR4 - SSD:三星980 NVMe PCIe3.0 500G - 西数:红盘4T - 散热器:利民AXP90 X47 - 电源:台达VX350 - 机箱:先马米立方matx 2...

Openwrt

Ubuntu系统备份

1. 备份 2. 还原 如果当前启动无法启动,可以通过live cd来启动并执行恢复操作

Xiaoya

windows下必须使用bridge模式 1. docker安装 2. 定时清理 3. 获取元数据 在wsl的ubuntu子系统中执行 4. 参考

Advanced Table

obsidian 功能 - 自动化格式表格 - Excel样式的表格导航,即使用Tab和Enter在行和列之间导航 - 对指定的列进行函数求值 - 添加、删除、移动行和列 - 设置列的对齐方式 - 对指定列进行排序 - 将表格导出为CSV格式 公式 基本格式如下: 如中的 代表最后一行、第二列,右边...

Appearance

打开笔记仓库的 .obsidian 文件夹,其中如果没有 snippets 文件夹则创建。在 Obsidian 中的 CSS snippets 都是以 .css 的档案格式储存在特定的文件夹。如果你有将 CSS snippets 放到该文件夹,就会在 Obsidian 显示开关。 1. 别人给你的,...

Dataview

1 查询依据 yaml数据/metainfo 2 使用查询语言 使用下列语法创建查询语言代码块 dataview query command 3 使用内联查询 内联查询的结果只能是一个,不能查询一个列表。当前页面可以通过获得,其他页面可以通过双链语法获得 通过下列语法创建内联查询: 此博客文件名: ...

Excalidraw

obsidian 是一个手工风格的白板工具。可以使用呼出命令菜单,输入excalidraw进行创作。 library 提供了很多公开的模板库可以帮助我们画出很多精美的图案。 导出 可以导出为png或svg 双链 鼠标右键选中create link,可以在excalidraw中使用双链。和在markd...

Image Auto Upload Plugin

obsidian 使用aliyun oss作为obsidian图床 1. 下载 2. 配置oss作为图床 3. 在obsidian中粘贴图片后自动上传aliyun os

Mind Map

obsidian 1. 使用方式 使用呼出命令行,输入通过提示补全命令 2. pin 可以将思维导图的预览面板嵌到当前笔记中。 3. copy screenshot 将svg格式mind map复制到剪切板 4. bug修改 mind map已经很久没有维护了,对于代码块支持存在bug,会导致无法生...

Minimal Theme Setting

obsidian 设置主题 settings -> options -> appearance里面选择主题 Style settings 自定义包括字体等各种样式 Minimal Theme setting 里面内置了一些经典的配色,可以对主题进行一些快速设置

Obsidian Tasks

任务管理 常用语法 1. 今日之前(包括)已完成,done before 2. 本周截止当前日(包括)已完成,done after 3. 根据重要性排序,sort by priority reverse 示例 获取本周所有已经完成的任务,按优先级倒序排列 tasks done before done...

Obsidian总览

obsidian 1. 视图 obsidian一共提供了三种视图: - preview mode:预览模式,可以编辑,显示实时预览结果,此模式下metadata不会渲染 - reading mode:阅读模式,markdown渲染后结果,不可编辑 - source mode:以纯文本形式显示mark...

代理

ssh代理

效率工具

1. 添加环境变量 2. windows命令行代理 3. git代理 也可以直接编辑/.gitconfig文件 4. windows配置beyondcompare作为gitdiff 修改.gitconfig配置文件 5. 删除重复文件

Vault

Tcp异常断链

1. 现象 客服访问建行业务偶现超时。 建行server端主动发起断链,发送fin报文,客户B收到后回ACK报文,进入fin-wait-2状态 客户B服务端前设置有防火墙,fin状态下设置10秒超时,客户B超时后的rst报文被防火墙丢了,建行server无法收到rst报文,导致一直处于fin-wai...

业务偶现超时

1. 现象 客户A通过NAT网关访问客户B业务偶现超时。 2. 抓包分析 中间设备抓包发现,服务端主动断链,发送FIN报文并收到ACK报文完成两次挥手,但客户端并没有随之进行被动关闭,直到约20秒后发出rst报文,在此期间连接一直处于半关闭状态。而NAT网关fin超时时间为5秒,此时会话已经老化,再...

交换机选型要点

交换机选型

clos架构

CLOS网络的核心思想是:用多个小规模、低成本的单元构建复杂,大规模的网络。简单的CLOS网络是一个三级互连架构,包含了输入级,中间级,输出级。下图中的矩形都是规模小得多的转发单元,相应的成本也很低。简单来说,CLOS就是一种多级交换架构,在输入输出增长的情况下,增加中间交换单元的数量,而不是每个交...

NAT分片报文

前文我们聊到,在NAT地址转换中,NAT除了对IP地址转换外,还使用到TCP或UDP报文的端口号、ICMP报文的ICMP头中的identifier字段信息。当一个IP分片报文和TCP分段报文 1|IP报文被分成若干片之后,这些信息只有首片报文会携带,后续分片报文依靠报文ID、分片标志位、分片偏移量依...

NAT之ICMP

报文没有类似于或的端口信息,而正常NAT为了内网的安全性和IP地址的高利用率,一般都使用了五元组来匹配NAT会话。为了对ICMP的请求报文进行NAT映射,又要确保网络的安全性,只允许合法的ICMP响应报文进入内网,就必须对其进行特殊的处理。 1. ICMP请求和应答报文 ICMP的request和r...

NAT会话

我们知道传输层的任意一条流都是通过两个建立的,由组成,因此一条流可以用五元组表示。这个五元组中的任意一个元素都不能改变,否则就是另外一条流了。对于服务端来说,和中任意一个变了,就意味着一个新的接入连接;而对于客户端来说,和中任意一个发生变化,访问的就是一个新的服务,比如通常是一个服务,是一个数据库的...

Thrift协议

是一种轻量化、语言无关的RPC框架。主要包含三大部分:代码生成、序列化框架、RPC框架。 Thrift网络协议栈 采用的是模型,网络协议栈从下到上分别为:、、、。 Transport 传输层为网络提供了一个简单的抽象,定义了具体的网络传输协议。其本质是一个提供了通过各种协议进行通信的框架,支持包括和...

shell编程

本文主要介绍shell编程的基本语法以及实际应用中的常见命令。 1. 注释 1.1. 单行注释 1.2. 多行注释 多行注释也可替换成或 2. 变量 2.1. 变量定义 使用的形式,VALUE如果是字符串的话,可以使用单引号、双引号或者不加引号。单引号内的任何字符都会原样输出,不能进行转义,单引号内...

netstat命令

是基于Netstat这个命令行工具的指令,它可以用来查询系统上的网络套接字连接情况,包括,以及。另外它还能列出路由表,接口状态和多播成员等信息。 1. 参数选项 | 参数 | 作用 | | | --- | -----------------------------------------------...

系统信息查看命令汇总

在系统下经常要查看各种信息,需要使用到各种命令,全部背下来也是一项不小的工作量。每次临时找也比较浪费时间,这里汇总下常用的命令。 1. 系统 1.1. 查看linux内核版本 文件系统不是普通的文件系统,而是系统内核的映像。也就是说,该目录中的文件是存放在系统内存之中的,它以文件系统的方式为访问系统...

数据库隔离级别

本文主要目的是阐明数据库的四种隔离级别以及在mysql下实现隔离的原理。 1. 隔离级别 数据库事务隔离级别从低到高分别是:read uncommitted、read committed、retpeatable read、serializable,依次解决了数据库脏读、不可重复读、幻读问题。 - 脏...

用户和权限

使用 Linux 系统,不免会和用户和权限打交道,本文介绍了根权限和文件的权限属性等概念和应用。 1. 用户与群组 使用查看所有用户 使用查看所有用户组 修改文档所有者或群组 2. Linux权限 命令 - 代表三种身份owner/group/other,a代表全部身份all - 代表三种操作行为(...

进程管理

在 系统中,进程是资源调度的最小单位,进程的管理关乎着你使用系统的体验。 1. 进程类型 Linux 系统里有几种不同类型的进程:用户进程(User processes)、守护进程(Deamon processes)和内核进程(Kernel processes)。 1.1. 用户进程 系统里大多数进...

NAT概览

1. 背景 地址使用4个字节进行存储,最多能够提供个地址。随着互联网尤其是物联网的发展,全球地址早已不够用,因此人们发明了(网络地址转换)来缓解这个问题。 简单来说,国际互联网组织划分了三个网络地址段作为内部网络本地通信使用,分别是,,。大部分内部机器都使用这些网段中的私有地址,如果它们需要访问公网...

NAT Overview

1. What is NAT? 1.1. NAT(Level 4) NAT(Network address translation)即网络地址转换,工作在OSI模型的三层或四层(PNAT),用于修改IP数据包中的IP地址和端口。当在专用网内部的一些主机本来已经分配到了local ip地址,但又想和I...

iptables

iptables 是 Linux 防火墙工作在用户空间的管理工具,是 netfilter/iptablesIP 数据包过滤系统是一部分,用来设置、维护和检查 Linux 内核的 IP 数据包过滤规则。 1. 四表五链 数据包在经过每个的时候会按照每个链对应的表依次进行查询匹配执行的操作,如PRERO...

tcpdump

tcpdump是一个优秀的网络分析工具,提供了强大且简单的接口。 1. options - -i any 监听所有的网卡接口,用来查看是否有网络流量 - -i eth0 只监听eth0网卡接口 - -D 显示可用的接口列表 - -n 不要解析主机名 - -nn 不要解析主机名或者端口名 - -q 显...

数据中心网络架构

0.1. 数据中心 根据维基百科释义,指用于安置计算机系统及相关部件的设施,例如电信和存储系统。数据中心是全球协作的特定设备网络,用来在internet网络基础设施上传递、加速、展示、计算和存储数据信息。 0.2. 传统数据中心网络架构 如图1所示,传统的大型数据中心网络通常采用三层架构。cisco...

应用层DNS协议

(Domain Name System)域名解析服务采用架构,是一个应用层协议。的作用是将人类可读的域名(如:www.shinerio.cc) 转换为机器可读的 IP 地址(如:1111.111.111.111)。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。协议建立...

计算机网络(九)—— 传输层

传输层架构在网络层之上,在两台计算机进程之间传输数据,常见的传输层协议包括TCP和UDP。 1. TCP 1.1. 首部格式 1.2. TCP状态机 TCP是面向连接的,在其生命周期会有各种不同状态 | 状态 | 描述 | | ------------ | ---------------------...

路由协议

路由是选择路径并将报文沿着选择的路径进行转发的过程。 1. 路由器 1.1. 路由器功能 路由器从功能上可以划分为: - 路由选择:使用路由协议(或手工静态配置的方式)获取整个网络的拓扑结构,构造、维护路由表。 - 分组转发:分组转发结构由交换结构、一组输入端口和一组输出端口构成。 1. IP分组检...

计算机网络(七)—— 网络层 —— ICMP

互联网控制消息协议(Internet Control Message Protocol,ICMP)是TCP/IP协议族的核心协议之一,用于IP协议中发送控制消息,提供可能发生在通信环境中的各种问题反馈。通过这些消息,使管理者可以对所发生的的问题作出诊断,然后采取适当的措施解决。ICMP可以简单认为是...

数据链路层概述

数据链路层使用的信道主要分为以下两种: - 点对点信道,使用一对一的点对点的通信方式 - 广播信道,使用一对多的广播通信方式,广播信道上连接的主机很多,因此必须使用专用的共享信道协议来协调这些主机的数据发送。 0.1. 数据链路 当需要在一条线路上传送数据时,除了必须有一条物理线路外,还必须有一些必...

计算机网络(三)—— 网络的基本分类

本文主要介绍计算机网络的分类以及局域网技术。 0.1. 网络分类 - 地理位置: 1. 个域网(PAN,Personal Area Network)。个域网允许设备围绕一个人进行通信。一个常见的例子是计算机通过无线网络(蓝牙)与其外围设备(显示器、键盘、鼠标。打印机)连接。 2. 局域网(LAN,L...

计算机网络(二)—— 性能指标

计算机网络的常见的性能指标有速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率。 1. 速率(bit/s或byte/s) 速率是物理层概念,指的是信道上每秒钟传输的0/1比特数量。比如下载文件中显示的7.8MB/s指的是每秒中可以传输7.8M字节的信息。 2. 带宽(bit/s) 带宽是逻辑概念...

计算机网络(一)—— 分层模型

计算机网络按照使用范围可以划分为个域网、局域网、城域网、广域网和互联网,其组成复杂,存在着大量诸如集线器、交换机、路由器、笔记本、手机等硬件。为了降低网络设计的复杂性,现阶段大部分网络都会形成一个层次栈结构,每一层都建立在其下一层的基础之上,向上一层提供特定服务。这种分层结构的概念其实和计算机领域的...

加密算法

本文主要介绍密码学中常见的两种加密算法—对称加密和非对称加密。 1. 散列(摘要)算法 在学习加密算法之前,我们先来了解一下散列算法(散列不是加密)。散列算法是通过一定方式对原文进行计算,产生一个哈希值,不管原始数据是什么样的,得到的哈希值都是固定长度的,其作用只是为了验证数据的完整性和唯一性,无法...

© 2026 shinerio. All rights reserved.

hello' ];then echo 'hello';fi\r\nstr='hello'\r\nif [ $str ];then echo 'hello';fi\r\n# hello\r\n```\r\n\r\n注意字符串的等值判断是一个等号`\"=\"`,与下文要讲的逻辑运算符使用两个等号`\"==\"`做等值判断不一样。\r\n# 4. 文件测试运算符\r\n\r\n| 操作符 | 说明 |\r\n| --------- | --------------------------------------- |\r\n| `-b file` | 检测文件是否是块设备文件,如果是,则返回true。 |\r\n| `-c file` | 检测文件是否是字符设备文件,如果是,则返回true。 |\r\n| `-d file` | 检测文件是否是目录,如果是,则返回true。 |\r\n| `-f file` | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回true。 |\r\n| `-g file` | 检测文件是否设置了`SGID`位,如果是,则返回true。 |\r\n| `-k file` | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回true。 |\r\n| `-p file` | 检测文件是否是有名管道,如果是,则返回true。 |\r\n| `-u file` | 检测文件是否设置了`SUID`位,如果是,则返回true。 |\r\n| `-r file` | 检测文件是否可读,如果是,则返回true。 |\r\n| `-w file` | 检测文件是否可写,如果是,则返回true。 |\r\n| `-x file` | 检测文件是否可执行,如果是,则返回true。 |\r\n| `-s file` | 检测文件是否为空(文件大小是否大于0),不为空返回true。 |\r\n| `-e file` | 检测文件(包括目录)是否存在,如果是,则返回true。 |\r\n\r\n```shell\r\nif [[ -f '/home' ]];then echo 'directory';else echo 'file';fi\r\n# file\r\nif [[ -d '/home' ]];then echo 'directory';else echo 'file';fi\r\n# directory\r\nif [[ -e '/tmp/fake_file' ]];then echo 'exist';else echo 'not exist';fi\r\n# not exist\r\n```\r\n\r\n# 5. 数组\r\n`shell`只支持一维数组,用括号表示,元素通过空格分隔,形如`数组名=(值1 值2 ... 值n)`。`shell`数组下标从`1`开始。\r\n- `${数组名[下标]}`,读取指索引位置数组元素\r\n- 使用`@`或`*`代替下标,获取数组中所有元素。\r\n- 使用`{#数组名[@]}`或`{#数组名[*]}`获取数组元素个数。\r\n\r\n```shell\r\narr=(value1 value2 value3 value4)\r\necho ${arr[3]}\r\n# value3\r\necho ${arr[*]} \r\n# value1 value2 value3 value4\r\necho ${arr[@]} \r\n# value1 value2 value3 value4\r\narr[3]='new_value3'\r\necho $arr[3]\r\n# new_value3\r\necho ${#arr[@]}\r\n# 4\r\necho \"arr[1]的长度为:${#arr[1]}\" \r\n# arr[1]的长度为:6\r\n```\r\n\r\n# 6. 运算符\r\n## 6.1. 赋值运算符\r\n```shell\r\na=10\r\nb=$a\r\n```\r\n## 6.2. 算术运算符\r\n算数运算符支持`+,-,*,/,%`\r\n### 6.2.1. expr表示法\r\n```shell\r\necho `expr 10 + 20`\r\n# 30\r\necho $(expr 20 / 2)\r\n# 10\r\necho $(expr 10 \\* 20) # 使用expr进行乘法运算时必须使用\\进行转义\r\n# 200\r\n```\r\n> 使用expr表示方法时,表达式和运算符之间必须有空格。\r\n### 6.2.2. `$[]`\r\n```shell\r\necho $[10%3]\r\n# 1\r\n```\r\n### 6.2.3. `$(())`\r\n```shell\r\necho $((10*20))\r\n# 200\r\n```\r\n\r\n## 6.3. 关系运算符\r\n最常见的关系运算符是:` ==,!=,<,>,>=,<= `\r\n```shell\r\necho $[10==10]\r\n# 1\r\nflag=$[20==10]\r\necho $flag\r\n# 1\r\necho $[10>=10]\r\n# 1\r\n```\r\n如下关系运算符只能使用在条件表达式`if、while`下,类似`flag=[ $a -eq $b ]`的直接赋值语句是非法的。关系运算符的条件表达式要放在**一对**方括号中,并且中间需要有空格。也可以使用`(())`替代`[]`,此时`-eq,-lt,-le`之类的需要使用` ==,<,<= `等替换。\r\n\r\n> `[]`搭配`-eq -ne`等使用,`(())`搭配`|| &&`等使用。\r\n\r\n| 运算符 | 说明 | 举例 |\r\n| ------ | ----------------------------------------------------- | --------------- |\r\n| `-eq` | 检测两个数是否相等,相等返回true。 | `[ $a -eq $b ]` |\r\n| `-ne` | 检测两个数是否不相等,不相等返回 true。 | `[ $a -ne $b ]` |\r\n| `-gt` | 检测左边的数是否大于右边的,如果是,则返回 true。 | `[ $a -gt $b ]` |\r\n| `-lt` | 检测左边的数是否小于右边的,如果是,则返回 true。 | `[ $a -lt $b ]` |\r\n| `-ge` | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | `[ $a -ge $b ]` |\r\n| `-le` | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | `[ $a -le $b ]` |\r\n\r\n```shell\r\nif [ 1 -eq 1 ];then echo 'hello';fi;\r\n# hello\r\nif ((10>=10));then echo 'hello';fi;\r\n# hello\r\n```\r\n\r\n## 6.4. 布尔、逻辑运算符\r\n布尔、逻辑运算的条件表达式也要放在一对方括号中`[]`。\r\n\r\n| 运算符 | 说明 | 举例 |\r\n| ----- | ----- | ----- |\r\n| `!` | 非运算,表达式为true则返回false,否则返回true。 | `[ ! 10 -eq 0 ]` |\r\n| `-o` | 或运算,有一个表达式为true则返回true。 | `[ 10 -lt 20 -o 10 -gt 100 ]` |\r\n| `-a` | 与运算,两个表达式都为true才返回true。 | `[ 10 -lt 20 -a 10 -gt 100 ]` |\r\n\r\n```shell\r\nif [ ! 10 -eq 0 ];then echo 'hello';fi\r\n# hello\r\nif [ 10 -ge 10 -a 10 -ge 10 ];then echo 'hello';fi\r\n# hello\r\nif [ 10 -ge 20 -o 10 -ge 0 ];then echo 'hello';fi\r\n# hello\r\n```\r\n\r\n `-o`也可以使用`||`替代,`-a`也可以使用`&&`替代,此时需要使用`(())`或`[[]]`替代`[]`。\r\n\r\n```shell\r\nif [[ 10==10 && 5==5 ]];then echo 'hello';fi\r\n# hello\r\nif ((10==10 && 5==5));then echo 'hello';fi\r\n# hello\r\n```\r\n\r\n# 7. 传递参数\r\n在执行 `Shell`脚本时,向脚本传递参数,脚本内获取参数的格式为:`$n`。`n`代表一个数字,`1`为执行脚本的第一个参数,`2`为执行脚本的第二个参数,`$0`为执行的文件名。在为`shell`脚本传递的参数中如果包含空格,应该使用单引号或者双引号将该参数括起来,以便于脚本将这个参数作为整体来接收。另外,还有几个特殊字符用来处理参数:\r\n\r\n| 参数处理 | 说明 |\r\n| -------- | ---------------------------------------------------------------------------------------- |\r\n| `$#` | 传递到脚本的参数个数 |\r\n| `$*` | 以一个单字符串显示所有向脚本传递的参数,以`$1 $2 … $n`的形式输出所有参数。 |\r\n| `$` | 脚本运行的当前进程ID号 |\r\n| `$!` | 后台运行的最后一个进程的ID号 |\r\n| `$@` | 与`$*`相同,但是使用时加引号,并在引号中返回每个参数,以`$1 $2 … $n`的形式输出所有参数。 |\r\n| `$-` | 显示Shell使用的当前选项,与set功能相同。 |\r\n| `$?` | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |\r\n\r\n编写`shell`脚本如下:\r\n```bash\r\nset -e\r\necho \"执行的文件名:$0\"\r\necho \"参数个数为$#\"\r\necho \"第一个参数为:$1\"\r\necho \"第二个参数为:$2\"\r\necho \"所有参数为:$@\"\r\necho \"进程ID号为:$\"\r\necho \"当前shell选项为$-\"\r\necho \"上一个命令退出的状态为$?\"\r\n```\r\n\r\n执行结果如下:\r\n```shell\r\n$ sh test.sh param1 \"param2 with space\"\r\n执行的文件名:test.sh\r\n参数个数为2\r\n第一个参数为:param1\r\n第二个参数为:param2 with space\r\n所有参数为:param1 param2 with space\r\n进程ID号为:3700\r\n当前shell选项为ehB\r\n上一个命令退出的状态为0\r\n```\r\n\r\n# 8. 函数\r\n函数定义通过`function_name(){}`的形式定义,函数返回值可以显示用return返回,如果不加,将以最后一条命令的运行结果作为返回值,函数返回值只能是数字。函数也可以传递参数,但是函数定义的()中不需要申明。\r\n\r\n编写`shell`脚本如下:\r\n\r\n```shell\r\nsayhello(){\r\n echo \"hello\"\r\n}\r\n\r\n# 注意函数调用只需要函数名,不需要加()\r\nsayhello\r\n\r\nsayhellowithparam(){\r\n echo \"hello, $1 $2 $3\"\r\n return 1\r\n}\r\n\r\nsayhellowithparam zhangsan lisi wangwu\r\necho $?\r\n```\r\n\r\n输出如下:\r\n> hello\r\n> hello, zhangsan lisi wangwu\r\n> 1\r\n\r\n# 9. 命令替换\r\n命令替换可以将一个命令的输出作为另外一个命令的参数。\r\n\r\n```shell\r\n# command1 `command2`\r\ncd `pwd` # 该命令将pwd命令列出的目录作为cd命令的参数,结果仍然是停留在当前目录下\r\ncd `echo $HOME` # 将echo的输出作为cd的参数,即打开用户根目录\r\n```\r\n\r\n# 10. let命令\r\nlet 命令是 BASH 中用于计算的工具,用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量。如果表达式中包含了空格或其他特殊字符,则必须引起来。\r\n- 自加操作:`let no++`\r\n- 自减操作:`let no--`\r\n- 简写形式 `let no+=10`,`let no-=20`,分别等同于`let no=no+10`,`let no=no-20`\r\n# 11. 流程控制\r\n## 11.1. if\r\n```shell\r\nif ((10<2));then\r\n echo \"10小于2\"\r\nelif ((10>20));then\r\n echo \"a大于10\"\r\nelse\r\n echo \"10大于2且小于10\"\r\nfi\r\n```\r\n## 11.2. case\r\n`case`每一种情况以右括号结尾,执行到`;;`结束\r\n\r\n```shell\r\ncase 10 in\r\n0|1)\r\n echo \"a为0或1\" \r\n ;;\r\n11|12) \r\n echo \"a为11或12\"\r\n ;;\r\n*) \r\n# *用来匹配所有情况\r\n echo \"a为10\" \r\n ;;\r\nesac\r\n```\r\n\r\n## 11.3. while\r\n```shell\r\ncount=0\r\nwhile(($count<5))\r\ndo\r\n echo $count\r\n let count++\r\ndone\r\n```\r\n\r\n## 11.4. util\r\n```shell\r\ncount=0\r\nuntil(($count>=5))\r\ndo\r\n echo $count\r\n let count++\r\ndone\r\n```\r\n\r\n## 11.5. for\r\n```shell\r\n# for写法1\r\nfor ((count=0;$count<5;count=$count+1))\r\ndo\r\n echo $count\r\ndone\r\n\r\n# for写法2\r\narr=(0 1 2 3 4)\r\nfor count in ${arr[*]}\r\ndo\r\n echo $int\r\ndone\r\n\r\n# for写法3\r\nfor int in 0 1 2 3 4\r\ndo\r\n echo $int\r\ndone\r\n```\r\n\r\n## 11.6. break和continue\r\n用法与一般编程语言类似,break跳出循环,continue跳出当前循环\r\n```shell\r\nfor ((count=0;$count>=0;count=$count+1))\r\ndo\r\n    if (($count>=5));then\r\n        break;\r\n    fi\r\n    echo $count\r\ndone\r\n```\r\n\r\n# 12. 输入输出重定向\r\n## 12.1. 输出重定向\r\n`command > file`将命令输出结果保存的file文件中。`>`从文件头开始写,会覆盖旧内容,`>>`将内容追加到文件末尾\r\n```shell\r\n# 将who命令的输出保存的users文件中,可以通过cat users查看\r\nwho > users \r\n# 将字符串hello world保存到text.txt文件中\r\necho \"hello world\" > text.txt \r\n```\r\n\r\n## 12.2. 输入重定向\r\n`command < file`从file文件获取输入\r\n\r\n```shell\r\nwc -l /etc/passwd \r\n# 123 /etc/passwd\r\nwc -l < users\r\n# 123\r\n# 第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容\r\n\r\n# command < infile > outfile, 同时替换输入和输出,执行command,从文件infile读取内容,然后将输出写入到outfile中。\r\nwc -l < /etc/passwd > result\r\ncat result \r\n# 123\r\n```\r\n\r\n## 12.3. 文件描述符\r\n一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件\r\n- 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。\r\n- 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。\r\n- 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。\r\n\r\n```shell\r\n# 将标准错误输出重定向到error_trace\r\ncat /tmp/fake_file 2>error_trace\r\ncat error_trace\r\n# cat: /tmp/fake_fule: No such file or directory\r\n\r\n# 将标准输出和标准错误输出都重定向到result\r\ncat /tmp/fake_file &>result\r\ncat result\r\n# cat: /tmp/fake_fule: No such file or directory\r\n\r\n# 将标准错误重定向到标准输出\r\nls /tmp/fake_file > result 2>&1\r\ncat result\r\n# cat: /tmp/fake_fule: No such file or directory\r\n\r\n# 将标准输入重定向到result,将标准错误输出重定向到error_trace\r\ncat /tmp/fake_file 1>result 2>error_trace\r\n```\r\n\r\n## 12.4. Here Document\r\n将两个`EOF`之间的内容(document) 作为输入传递给`command`\r\n\r\n```bash\r\n# command << EOF\r\n# document\r\n# EOF\r\n\r\ncat << EOF\r\nhello\r\nshinerio\r\nlearn shell\r\nEOF\r\n```\r\n\r\n> 结尾的`EOF`一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和`tab`缩进。开始的`EOF`前后的空格会被忽略掉。\r\n\r\n## 12.5. /dev/null\r\n如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null。`command > /dev/null`\r\n\r\n```shell\r\ncat users > /dev/null\r\ncat users > /dev/null 2>&1\r\n```\r\n\r\n# 13. set命令\r\nset指令能设置所使用shell的执行方式,可依照不同的需求来做设置。以下只列出常用的选项参数:\r\n- -e  若指令传回值不等于0,则立即退出shell。\r\n- -f  取消使用通配符。\r\n- -n  只读取指令,而不实际执行。\r\n- -x  执行指令后,会先显示该指令及所下的参数。\r\n"},{"id":"netstat命令","title":"netstat命令","date":"2023-01-24T00:00:00.000Z","tags":["linux"],"readingTime":2,"slug":"netstat命令","description":"是基于Netstat这个命令行工具的指令,它可以用来查询系统上的网络套接字连接情况,包括,以及。另外它还能列出路由表,接口状态和多播成员等信息。 1. 参数选项 | 参数 | 作用 | | | --- | -----------------------------------------------...","relativePath":"Tech/Os/Linux/常用运维命令/netstat命令.md","rawContent":"---\r\ntitle: netstat命令\r\ndate: 2023-01-24\r\ncategories:\r\n- linux\r\ntags:\r\n- linux\r\n---\r\n\r\n`netstat`是基于Netstat这个命令行工具的指令,它可以用来查询系统上的网络套接字连接情况,包括`tcp`,`udp`以及`Unix套接字`。另外它还能列出路由表,接口状态和多播成员等信息。\r\n\r\n\r\n\r\n# 1. 参数选项\r\n\r\n| 参数 | 作用 | |\r\n| --- | ------------------------------------------------------------------- | --- |\r\n| -n | 使用数字形式表示网络地址 | |\r\n| -a | 可以列出系统中所有的网络连接,包括正在建立的连接、已经建立的连接以及处于监听状态的端口,也包括time-wait状态和udp端口监听。 | |\r\n| -c | 每隔一段时间自动执行netstat命令 | |\r\n| -s | 按照每隔协议来分类统计 | |\r\n| -t | 显示tcp相关选项 | |\r\n| -u | 显示udp相关选项 | |\r\n| -e | 显示额外信息 | |\r\n| -p | 显示与连接有关的程序名和进程 | |\r\n| -r | 显示路由信息,等效于`route -n` | |\r\n| -l | 显示监控中的服务器的Socket | |\r\n\r\n\r\n# 2. 常用命令\r\n## 2.1. 查看udp端口使用情况\r\nnetstat -anpu \r\n## 2.2. 查看所有监听端口\r\nnetstat -lntp \r\n## 2.3. 查看所有已经建立的连接\r\nnetstat -antp\r\n## 2.4. 查看网络统计信息\r\n```shell\r\nnetstat -s\r\nIp:\r\n 184695 total packets received\r\n 0 forwarded\r\n 0 incoming packets discarded\r\n 184687 incoming packets delivered\r\n 143917 requests sent out\r\n 32 outgoing packets dropped\r\n 30 dropped because of missing route\r\nIcmp:\r\n 676 ICMP messages received\r\n 5 input ICMP message failed.\r\n ICMP input histogram:\r\n destination unreachable: 44\r\n echo requests: 287\r\n echo replies: 345\r\n 304 ICMP messages sent\r\n 0 ICMP messages failed\r\n ICMP output histogram:\r\n destination unreachable: 17\r\n echo replies: 287\r\nTcp:\r\n 473 active connections openings\r\n 28 passive connection openings\r\n 4 failed connection attempts\r\n 11 connection resets received\r\n 1 connections established\r\n 178253 segments received\r\n 137936 segments send out\r\n 29 segments retransmited\r\n 0 bad segments received.\r\n 336 resets sent\r\nUdp:\r\n 5714 packets received\r\n 8 packets to unknown port received.\r\n 0 packet receive errors\r\n 5419 packets sent\r\nTcpExt:\r\n 1 resets received for embryonic SYN_RECV sockets\r\n ArpFilter: 0\r\n 12 TCP sockets finished time wait in fast timer\r\n 572 delayed acks sent\r\n 3 delayed acks further delayed because of locked socket\r\n 13766 packets directly queued to recvmsg prequeue.\r\n 1101482 packets directly received from backlog\r\n 19599861 packets directly received from prequeue\r\n 46860 packets header predicted\r\n 14541 packets header predicted and directly queued to user\r\n TCPPureAcks: 12259\r\n TCPHPAcks: 9119\r\n TCPRenoRecovery: 0\r\n TCPSackRecovery: 0\r\n TCPSACKReneging: 0\r\n TCPFACKReorder: 0\r\n TCPSACKReorder: 0\r\n TCPRenoReorder: 0\r\n TCPTSReorder: 0\r\n TCPFullUndo: 0\r\n TCPPartialUndo: 0\r\n TCPDSACKUndo: 0\r\n TCPLossUndo: 0\r\n TCPLoss: 0\r\n TCPLostRetransmit: 0\r\n TCPRenoFailures: 0\r\n TCPSackFailures: 0\r\n TCPLossFailures: 0\r\n TCPFastRetrans: 0\r\n TCPForwardRetrans: 0\r\n TCPSlowStartRetrans: 0\r\n TCPTimeouts: 29\r\n TCPRenoRecoveryFail: 0\r\n TCPSackRecoveryFail: 0\r\n TCPSchedulerFailed: 0\r\n TCPRcvCollapsed: 0\r\n TCPDSACKOldSent: 0\r\n TCPDSACKOfoSent: 0\r\n TCPDSACKRecv: 0\r\n TCPDSACKOfoRecv: 0\r\n TCPAbortOnSyn: 0\r\n TCPAbortOnData: 1\r\n TCPAbortOnClose: 0\r\n TCPAbortOnMemory: 0\r\n TCPAbortOnTimeout: 3\r\n TCPAbortOnLinger: 0\r\n TCPAbortFailed: 3\r\n TCPMemoryPressures: 0\r\n```\r\n\r\n\r\n\r\n\r\n\r\n"},{"id":"系统信息查看命令汇总","title":"系统信息查看命令汇总","date":"2023-01-24T00:00:00.000Z","tags":["linux"],"readingTime":2,"slug":"系统信息查看命令汇总","description":"在系统下经常要查看各种信息,需要使用到各种命令,全部背下来也是一项不小的工作量。每次临时找也比较浪费时间,这里汇总下常用的命令。 1. 系统 1.1. 查看linux内核版本 文件系统不是普通的文件系统,而是系统内核的映像。也就是说,该目录中的文件是存放在系统内存之中的,它以文件系统的方式为访问系统...","relativePath":"Tech/Os/Linux/常用运维命令/系统信息查看命令汇总.md","rawContent":"---\r\ntitle: 系统信息查看命令汇总\r\ndate: 2023-01-24\r\ncategories:\r\n- linux\r\ntags:\r\n- linux\r\n---\r\n\r\n在`Linux`系统下经常要查看各种信息,需要使用到各种命令,全部背下来也是一项不小的工作量。每次临时找也比较浪费时间,这里汇总下常用的命令。\r\n\r\n\r\n\r\n# 1. 系统\r\n\r\n## 1.1. 查看linux内核版本\r\n\r\n```shell\r\ncat /proc/version\r\n# Linux version 4.15.0-20-generic (buildd@lgw01-amd64-039) (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018\r\nuname -a\r\n# Linux glowing-kittens-3.localdomain 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux\r\n```\r\n\r\n`/proc`文件系统不是普通的文件系统,而是系统内核的映像。也就是说,该目录中的文件是存放在系统内存之中的,它以文件系统的方式为访问系统内核数据的操作提供接口。而我们使用命令`uname -a`的信息就是从该文件获取的,当然用方法二的命令直接查看它的内容也可以达到同等效果.另外,加上参数\"a\"是获得详细信息,如果不加参数为查看系统名称。\r\n\r\n## 1.2. 查看linux系统版本\r\n\r\n```shell\r\nlsb_release -a\r\n# Distributor ID:\tUbuntu\r\n# Description:\tUbuntu 18.04 LTS\r\n# Release:\t18.04\r\n# Codename:\tbionic\r\n# ==========================[STDERR]==========================\r\n# No LSB modules are available.\r\n# ===========================[END]============================\r\n\r\ncat /etc/issue\r\n# Ubuntu 18.04 LTS \\n \\l\r\n```\r\n\r\n## 1.3. 查看系统硬件信息\r\n```shell\r\n# 查看CPU信息\r\ncat /proc/cpuinfo \r\n# 查看内存信息\r\ncat /proc/meminfo\r\n# 列出所有pci设备\r\nlspci -tv \r\n# 列出所有USB设备\r\nlsusb -tv \r\n# 列出加载的内核模块\r\nlsmod \r\n```\r\n\r\n# 2. 资源占用\r\n\r\n```shell\r\n# 查看内存使用量和交换区使用量\r\nfree -m \r\n# 查看各分区使用情况\r\ndf -h \r\n# 查看指定目录的大小\r\ndu -sh \r\n# 查看系统运行时间、用户数、负载\r\nuptime \r\n# 查看系统负载\r\ncat /proc/loadavg \r\n```\r\n\r\n# 3. 磁盘和分区\r\n\r\n```shell\r\n# 查看挂接的分区状态\r\nmount | column -t \r\n# 查看所有分区\r\nfdisk -l \r\n# 查看所有交换分区\r\nswapon -s \r\n# 查看磁盘参数(仅适用于IDE设备)\r\nhdparm -i /dev/hda \r\n # 查看启动时IDE设备检测状况\r\ndmesg | grep IDE\r\n```\r\n\r\n# 4. 网络\r\n\r\n```shell\r\n# 查看所有网络接口的属性\r\nifconfig \r\nip a\r\n# 查看防火墙设置\r\niptables -L \r\n# 查看路由表\r\nroute -n \r\n```\r\n\r\n# 5. 用户\r\n\r\n```shell\r\n# 查看活动用户\r\nw \r\n# 查看指定用户信息\r\nid \r\n# 查看用户登录日志\r\nlast \r\n# 查看系统所有用户\r\ncut -d: -f1 /etc/passwd \r\n# 查看系统所有组\r\ncut -d: -f1 /etc/group \r\n# 查看当前用户的计划任务\r\ncrontab -l \r\n```"},{"id":"数据库隔离级别","title":"数据库隔离级别","date":"2023-01-23T00:00:00.000Z","tags":["database"],"readingTime":8,"slug":"数据库隔离级别","description":"本文主要目的是阐明数据库的四种隔离级别以及在mysql下实现隔离的原理。 1. 隔离级别 数据库事务隔离级别从低到高分别是:read uncommitted、read committed、retpeatable read、serializable,依次解决了数据库脏读、不可重复读、幻读问题。 - 脏...","relativePath":"Tech/Middleware/Database/数据库隔离级别.md","rawContent":"---\r\ntitle: 数据库隔离级别\r\ndate: 2023-01-23\r\ncategories:\r\n- middleware\r\ntags:\r\n- database\r\n---\r\n\r\n本文主要目的是阐明数据库的四种隔离级别以及在mysql下实现隔离的原理。\r\n\r\n\r\n\r\n# 1. 隔离级别\r\n数据库事务隔离级别从低到高分别是:read uncommitted、read committed、retpeatable read、serializable,依次解决了数据库脏读、不可重复读、幻读问题。\r\n- **脏读**:一个事务读到了另一个事务未提交的数据\r\n- **不可重复读**:在一个事务中多次读取数据过程中发生了另一个事务对数据进行了**更新**,导致前后两次查询数据结果不同,主要体现在数据库数据前后数据内容的不一致。\r\n-  **幻读**:一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。主要体现在数据库数据数目不一致的情况(serializable解决了这个问题,需要锁住满足条件的所有记录以及相近的记录)\r\n\r\n# 2. 隔离原理\r\nMysql的默认隔离级别是可重复读,是读已提交。数据库的隔离级别其实是依赖锁实现的。\r\n\r\n## 2.1. 读未提交\r\n读未提交其实就是数据库操作不会加锁,其实就是没有隔离。B事务能看到A事务已经修改但没有提交的数据,这时候如果A事务回滚,B事务看到的其实就是脏数据,一般情况下我们不会选用这种级别。\r\n\r\n## 2.2. 读已提交\r\n这个是pg、oracle等数据库的默认隔离级别。一个事务只能读到另一个事务已经提交的数据。在mysql中可重复读和读已提交都是通过`MVCC`进行实现的,区别在于可重读是事务启动的时候就生成read view整个事务结束都一直使用这个read view,而在读已提交中则是每执行一条语句就重新生成最新的read view。\r\n\r\n## 2.3. 可重复读\r\nmysql通过MVCC解决了不可重复读的问题,本质上是一种快照读。在事务开启的时候生成快照,后续读到的都是当时的快照数据,即使当前数据已经其他事务修改并提交。具体规则如下:\r\n1. 当前事务内的更新可以读到\r\n2. 其实事务未提交的不能读到\r\n3. 其他事务在快照创建后提交的不能读到\r\n4. 其他事务在快照创建前提交的可以读到(这种其实另一个事务已经结束了)\r\n\r\n如下,可以看到虽然B事务对数据进行了修改并提交,但是事务A第二次读到的只还是10。\r\n\r\n![[mysql可重复读]]\r\n\r\n我们在`postgre`下进行相同的测试就可以发现`postgre` 默认隔离级别是读已提交。\r\n\r\n![[postgresql不可重复读]]\r\n\r\n可以通过命令行设置当前事务隔离级别,即可以得到和mysql相同的预期。\r\n\r\n```shell\r\npostgres=# show default_transaction_isolation;\r\n default_transaction_isolation \r\n-------------------------------\r\n read committed\r\n(1 row)\r\npostgres=# begin;\r\nBEGIN\r\npostgres=*# set default_transaction_isolation='repeatable read';\r\nSET\r\n```\r\n\r\n通过行级锁可以解决并发写,修改的时候需要对数据行加锁,且在事务提交时才会释放。\r\n> 这里需要注意一下,如果更新的条件没有用到索引的话,mysql会对所有行加行锁,然后再判断不满足的行进行释放,这个过程其实也比较影响性能。所以,如果是大表的话,建议合理设计索引,如果真的出现这种情况,那很难保证并发度。\r\n\r\n## 2.4. 幻读\r\nmysql有两种读数据方式,一种是快照读(普通select),另一种是当前读(select ... for update,update,delete)。mysql可以完全解决快照读下的幻读问题,但是并不能完全解决当前读下的幻读问题。解决方式是使用了行锁+间隙锁,这个锁叫做 Next-Key锁。\r\n\r\n### 2.4.1. 快照读\r\nmysql通过mvcc解决了快照读下的幻读问题。当启动事务后执行查询会创建一个read view,后续的查询语句会利用这个read view在undo log版本中找到事务,即便其他事务后续插入或删除了新的数据,原事务也只会读原来的快照,避免了幻读问题。\r\n\r\n以下以`select ... for update` 举例说明,update和delete同理。\r\n\r\n![[mysql快照读下解决幻读]]\r\n\r\n### 2.4.2. 当前读\r\nmysql数据库中会为索引维护一套B+树,用来快速定位行记录。B+索引树是有序的,所以会把这张表的索引分割成几个区间。如下图被分成了(负无穷,10)、(10,30)、(30,正无穷)三个区间,这三个区间是可以加间隙锁的。注意,mysql的间隙锁依赖于索引,否则会为整个表加上间隙锁,即所有的数据插入、删除都无法执行。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/mysql%E9%97%B4%E9%9A%99%E9%94%81.png)\r\n\r\n如下操作,事务A对(10,30)区间增加了间隙锁,对数据行10和30增加的行锁,来保证不会出现幻读。\r\n![[mysql当前读下解决幻读]]\r\n\r\n> 使用等值条件会将值两端区间都增加间隙锁,如`price = 10` 或`price <= 10` 都会将(负无穷,10)和(10,30)两个区间加锁。\r\n\r\n注意mysql并没有真正意义上解决幻读的问题,如下场景可以看出同一事务的两次查询看到的是不同的现象,因此解决幻读的最好方式是及早使用当前读的方式对数据加间隙锁。\r\n\r\n![[mysql当前读下幻读问题]]\r\n\r\n## 2.5. 串行化\r\n要彻底解决幻读问题,只能采用串行化,简单粗暴地将所有sql命令串行化执行,显然这样会极大地影响数据库性能。"},{"id":"用户和权限","title":"用户和权限","date":"2023-01-23T00:00:00.000Z","tags":["linux"],"readingTime":5,"slug":"用户和权限","description":"使用 Linux 系统,不免会和用户和权限打交道,本文介绍了根权限和文件的权限属性等概念和应用。 1. 用户与群组 使用查看所有用户 使用查看所有用户组 修改文档所有者或群组 2. Linux权限 命令 - 代表三种身份owner/group/other,a代表全部身份all - 代表三种操作行为(...","relativePath":"Tech/Os/Linux/用户和权限.md","rawContent":"---\r\ntitle: 用户和权限\r\ndate: 2023-01-23\r\ncategories:\r\n- linux\r\ntags:\r\n- linux\r\n---\r\n\r\n使用 Linux 系统,不免会和用户和权限打交道,本文介绍了根权限和文件的权限属性等概念和应用。\r\n\r\n\r\n\r\n# 1. 用户与群组\r\n\r\n使用`cat /etc/passwd`查看所有用户\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20190830142723.png)\r\n\r\n使用`cat /etc/group`查看所有用户组\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20190830142837.png)\r\n\r\n 修改文档所有者或群组\r\n\r\n```shell\r\n# command [-options] [账号/群组] [文件或目录]\r\n# 改变文档所属用户或用户组,-R为可选参数,表示递归表更\r\nchown [-R] [账号] [文件或目录]\r\nchown [-R] [账号]:[群组] [文件或目录]\r\n# 改变文档所属用户组\r\nchgrp [-R] [群组] [文件或目录]\r\n```\r\n\r\n# 2. Linux权限\r\n\r\n命令\r\n```shell\r\nchmod | u g o a | + - = | r w x | 文档路径\r\n# 或\r\nchmod | xxx | 文档路径\r\n```\r\n\r\n- `u,g,o`代表三种身份owner/group/other,a代表全部身份all\r\n- `+-=`代表三种操作行为(添加/删除/设置权限) \r\n- rwx表示三种权限,也可以使用4/2/1或者他们的和作为权限,如5代表rx\r\n- xxx代表三位数字,rwx分别为4/2/1,三种权限相加可以得出一种身份的权限 \r\n\r\n示例:\r\n\r\n```shell\r\n# 给test文件设置所有人拥有所有权限\r\nchmod u=rwx,g=rwx,0=rwx test\r\nchmod ugo=rwx test\r\nchmod a=rwx test\r\nchmod ugo+rwx test\r\nchmod 777 test\r\n# 所有人添加执行权限\r\nchmod a+x test\r\n# 所有人删除写权限\r\nchmod a-w test\r\n```\r\n\r\n> 对于文件来说x表示文件可以被系统执行的权限,对于目录来说,x代表着可以进入目录的权限,即可以cd进入\r\n\r\n# 3. 文档属性\r\n\r\n使用命令`ls -al --full-time` 或者`ll`可以查看文件或者目录的所有属性\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20190830140704.png)\r\n\r\n共有7列,分别是:\r\n- 第一列(共10位) 第1位表示文档类型,`d`表示目录,-表示普通文件,l表示符号链接,s表示套接字,b表示块设备(光驱、磁盘),c表示字符设备(鼠标、键盘),p表示管道。其中s/b/c/p都是伪文件。第2-10位,共9位,分表对应owner/group/others的权限,rwx分表表示readable/writable/excutable,-表示没有当前权限。\r\n- 第二列 关联硬链接数,对于一个新建文件夹来说,有两个链接,对于一个新建文件来说,有一个链接。\r\n- 第三列 表示文档所属owner\r\n- 第四列 表示文档所属group\r\n- 第五列 表示文档大小,单位字节,可以通过`ls -h`选项以最合适的单位显示\r\n- 第六列 表示文档最后修改时间\r\n- 第七列 表示文档名,隐藏文件以`.`开头\r\n\r\n# 4. su 和 sudo\r\n\r\n在 Linux 中,`su`命令和`sudo`命令有着十分巨大的区别:\r\n- `su`命令会把你切换到根用户`root`\r\n- `sudo`会使用根权限来执行命令\r\n\r\n我们可以通过修改(需要 root 权限)下列文件的中的用户列表,来决定哪些用户可以执行 sudo 命令:\r\n\r\n```bash\r\nsudo /usr/sbin/visudo\r\n```\r\n\r\n默认情况下,这个列表如下所示:\r\n\r\n```shell\r\n#User privilege specification\r\nroot ALL=(ALL) ALL\r\n```\r\n \r\n每一个 sudo 行的语法是:\r\n\r\n```shell\r\nuser machine=(effective_user) command\r\n```\r\n\r\n通过上面的语法,我们可以授予某个用户 root 权限,其中每一个域代表:\r\n- `user`是新的`sudo`用户的用户名 \r\n- `machine`是`sudo`生效的主机名 \r\n- `effective_user`代表被允许执行命令的有效用户 \r\n- `command`代表这这个用户可以执行的一系列命令\r\n\r\n# 5. 详解 umask\r\n\r\n每一个文件和文件夹在被创建的时候都会被赋予一定的权限属性,这些值可以通过 umask 来指定。正如 umask 的名称所显示的那样,这个值本身其实就是一个可以禁用相应权限属性的掩码。\r\n\r\n> 掩码由一个有效的`4`位`8`进制数值。如果把少于`4`位的数值作为参数传入,高位会被用 0 补全。\r\n\r\n默认情况下,文件夹在被创建的时候能获取的权限属性是`777(rwxrwxrwx)`,文件在被创建的时候能获取的权限属性是`666(rw-rw-rw-)`,二者的值都可以被被 umask 的掩码给减掉。\r\n\r\n我们可以这样来查看当前的`umask`值:\r\n\r\n```shell\r\n$ umask\r\n022\r\n$ touch text_file\r\n$ mkdir text_dir\r\n$ ls -l\r\ndrwxr-xr-x  2 zhangrui  staff  64 Jan 22 23:15 text_dir\r\n-rw-r--r--  1 zhangrui  staff   0 Jan 22 23:15 text_file\r\n```\r\n\r\n我们可以通过如下方式修改`umask`值:\r\n```shell\r\n# 当前会话中的umask设定为077\r\n$ umask 077\r\n# 所有者添加所有权限,删除组或其他用户的所有权限\r\n$ umask u+rwx,g-rwx,o-rwx\r\n# 设置所有者具有所有权限,设置组或其他用户不具有任何权限\r\n$ umask u=rwx,g=,o=\r\ndrwx------  2 zhangrui  staff  64 Jan 22 23:26 **test_dir**\r\n-rw-------  1 zhangrui  staff   0 Jan 22 23:25 test_file\r\n```\r\n\r\n> 注意默认情况下文件创建不具备x权限,通过umask并不能给文件创建添加x权限,umask只能用来减权限,不能用于添加权限。\r\n\r\n如果想要系统上的所有用户或者指定用户都使用设定的`umask`值的话,我们需要把相应的设定写入`/etc/profile`或者指定的`~/.bashrc`文件中去。\r\n"},{"id":"进程管理","title":"进程管理","date":"2023-01-23T00:00:00.000Z","tags":["linux","thread"],"readingTime":27,"slug":"进程管理","description":"在 系统中,进程是资源调度的最小单位,进程的管理关乎着你使用系统的体验。 1. 进程类型 Linux 系统里有几种不同类型的进程:用户进程(User processes)、守护进程(Deamon processes)和内核进程(Kernel processes)。 1.1. 用户进程 系统里大多数进...","relativePath":"Tech/Os/进程/进程管理.md","rawContent":"---\r\ntitle: 进程管理\r\ndate: 2023-01-23\r\ncategories:\r\n- linux\r\ntags:\r\n- linux\r\n- thread\r\n---\r\n在 `linux`系统中,进程是资源调度的最小单位,进程的管理关乎着你使用`linux`系统的体验。\r\n# 1. 进程类型\r\nLinux 系统里有几种不同类型的进程:用户进程(User processes)、守护进程(Deamon processes)和内核进程(Kernel processes)。\r\n## 1.1. 用户进程\r\n系统里大多数进程都是用户进程。用户进程由通常的用户账户启动,并在用户空间(user space)当中执行。在没有获得额外许可的情况下,通常用户进程无法对处理器进行特殊访问,或是访问启动进程的用户无权访问的文件。\r\n## 1.2. 守护进程\r\n守护进程通常是后台程序,它们往往由一些持续运行的服务来管理。守护进程可以用来监听请求,而后访问某些服务。举例来说,`httpd`这一守护进程监听访问网络页面的请求。守护进程也可以用来自行启动一些任务。例如,`crond` 这一守护进程会在预设的时间点启动计划任务。\r\n\r\n尽管用于管理守护进程的服务通常是 `root` 用户启动的,但守护进程本身往往以非 `root` 用户启动。这种启动方式,符合「只赋予进程运行所必须的权限」之要求,因而能使系统免于一些攻击。举例来说,若是黑客骇入了 `httpd` 这一由 `Apache` 用户启动的守护进程,黑客仍然无法访问包括 `root` 用户在内的其他用户的文件,或是影响其他用户启动的守护进程。\r\n\r\n守护进程通常由系统在启动时拉起,而后一直运行到系统关闭。当然,守护进程也可以按需启动和终止,以及让守护进程在特定的系统运行级别上执行,或是在运行过程中触发重新加载配置信息。\r\n\r\n## 1.3. 内核进程\r\n内核进程仅在内核空间(kernel space)当中执行。内核进程与守护进程有些相似,它们之间主要的不同在于:\r\n1. 内核进程对内核数据结构拥有完全的访问权限。\r\n2. 内核进程不如守护进程灵活:修改配置文件并触发重载即可修改守护进程的行为;但对于内核进程来说,修改行为则需要重新编译内核本身。\r\n# 2. 进程状态\r\n`linux`是一个多用户、多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态。同一时间同一`CPU`上只能运行一个进程,其他进程只能等待,因此我们可以宽泛地将进程状态分为:\r\n- 在CPU上执行,此时进程正在运行\r\n- 不在CPU上执行,此时进程不在运行\r\n\r\n进一步来讲,未在运行的进程也可能处于不同的状态:\r\n- TASK_RUNNING\r\n- TASK_INTERRUPTIBLE\r\n- TASK_UNINTERRUPTIBLE\r\n- TASK_STOPPED/TASK_TRACED\r\n- TASK_DEAD - EXIT_ZOMBIE\r\n- TASK_DEAD - EXIT_DEAD\r\n\r\n## 2.1. `R`(TASK_RUNNING,可执行状态)\r\n只有在该状态的进程才可能在CPU上运行。而同一时刻可能有多个进程处于可执行状态,这些进程的task_struct结构(进程控制块)被放入对应CPU的可执行队列中(一个进程最多只能出现在一个CPU的可执行队列中)。进程调度器的任务就是从各个CPU的可执行队列中分别选择一个进程在该CPU上运行。很多操作系统教科书将正在CPU上执行的进程定义为RUNNING状态、而将可执行但是尚未被调度执行的进程定义为READY状态,这两种状态在linux下统一为`TASK_RUNNING`状态。\r\n\r\n## 2.2. `S`(TASK_INTERRUPTIBLE,可中断状态)\r\n这个状态的进程因为等待某事件的发生(比如等待socket连接、等待信号量等)而被挂起,然后当这些事件发生或完成后,对应的等待队列中的一个或多个进程将被唤醒。一般情况下,系统中的大部分进程都处于这个状态。因为系统的CPU数量是有限的,而系统的进程数量是非常多的,所以大部分进程都处于睡眠状态。\r\n\r\n## 2.3. `D`(TASK_UNINTERRUPTIBLE,不可中断状态)\r\n与`TASK_INTERRUPTIBLE`状态类似,进程处于睡眠状态,但是此刻进程是不可中断的。不可中断,指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。\r\n\r\n绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。否则你将惊奇的发现,`kill -9`竟然杀不死一个正在睡眠的进程了!因此我们也很好理解`ps`命令看到的进程几乎不会出现`TASK_UNINTERRUPTIBLE`状态,而总是`TASK_INTERRUPTIBLE`状态。\r\n\r\n`TASK_UNINTERRUPTIBLE`状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。在进程对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用`TASK_UNINTERRUPTIBLE`状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。这种情况下的`TASK_UNINTERRUPTIBLE`状态总是非常短暂的,通过ps命令基本上不可能捕捉到。\r\n\r\n`linux`系统中也存在容易捕捉的`TASK_UNINTERRUPTIBLE`状态。执行`vfork`系统调用后,父进程将进入`TASK_UNINTERRUPTIBLE`状态,直到子进程调用`exit`或`exec`。 \r\n通过下面的代码就能得到处于`TASK_UNINTERRUPTIBLE`状态的进程:\r\n\r\n```c\r\n#include   \r\nvoid main() {  \r\n\tif (!vfork()) {\r\n\t\tsleep(100);  \r\n\t}\r\n}\r\n```\r\n\r\n## 2.4. `T` (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态\r\n向进程发送一个`SIGSTOP`信号,它就会因响应该信号而进入`TASK_STOPPED`状态(除非该进程本身处于`TASK_UNINTERRUPTIBLE`状态而不响应信号)。(`SIGSTOP`与`SIGKILL`信号一样,是非常强制的。不允许用户进程通过`signal`系列的系统调用重新设置对应的信号处理函数。) 向进程发送一个`SIGCONT`信号,可以让其从`TASK_STOPPED`状态恢复到`TASK_RUNNING`状态。\r\n\r\n当进程正在被跟踪时,它处于`TASK_TRACED`这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。比如在`gdb`中对被跟踪的进程下一个断点,进程在断点处停下来的时候就处于`TASK_TRACED`状态。而在其他时候,被跟踪的进程还是处于前面提到的那些状态。\r\n\r\n对于进程本身来说,`TASK_STOPPED`和`TASK_TRACED`状态很类似,都是表示进程暂停下来。而`TASK_TRACED`状态相当于在`TASK_STOPPED`之上多了一层保护,处于`TASK_TRACED`状态的进程不能响应`SIGCONT`信号而被唤醒。只能等到调试进程通过`ptrace`系统调用执行`PTRACE_CONT`、`PTRACE_DETACH`等操作(通过`ptrace`系统调用的参数指定操作),或调试进程退出,被调试的进程才能恢复`TASK_RUNNING`状态。\r\n\r\n## 2.5. `Z`(TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程\r\n进程在退出的过程中,处于TASK_DEAD状态。在这个退出过程中,进程占有的所有资源将被回收,除`task_struct`结构(以及少数资源)以外。于是进程就只剩下`task_struct`这么个空壳,故称为僵尸。 \r\n\r\n之所以保留`task_struct`,是因为`task_struct`里面保存了进程的退出码、以及一些统计信息。而其父进程很可能会关心这些信息。比如在`shell`中,`$?`变量就保存了最后一个退出的前台进程的退出码,而这个退出码往往被作为if语句的判断条件。 \r\n\r\n当然,内核也可以将这些信息保存在别的地方,而将`task_struct`结构释放掉,以节省一些空间。但是使用`task_struct`结构更为方便,因为在内核中已经建立了从`pid`到`task_struct`查找关系,还有进程间的父子关系。释放掉`task_struct`,则需要建立一些新的数据结构,以便让父进程找到它的子进程的退出信息。\r\n\r\n父进程可以通过wait系列的系统调用(如wait4、waitid)来等待某个或某些子进程的退出,并获取它的退出信息。然后wait系列的系统调用会顺便将子进程的尸体(task_struct)也释放掉。子进程在退出的过程中,内核会给其父进程发送一个信号,通知父进程来“收尸”。这个信号默认是`SIGCHLD`,在通过`clone`系统调用创建子进程时,可以设置这个信号。\r\n\r\n## 2.6. `X` (TASK_DEAD - EXIT_DEAD),退出状态,进程即将被销毁\r\n\r\n进程在退出过程中也可能不会保留它的`task_struct`。比如这个进程是多线程程序中被detach过的进程(进程?线程?参见《linux线程浅析》)。或者父进程通过设置SIGCHLD信号的handler为SIG_IGN,显式的忽略了SIGCHLD信号。(这是posix的规定,尽管子进程的退出信号可以被设置为SIGCHLD以外的其他信号。) \r\n此时,进程将被置于EXIT_DEAD退出状态,这意味着接下来的代码立即就会将该进程彻底释放。所以EXIT_DEAD状态是非常短暂的,几乎不可能通过ps命令捕捉到。\r\n\r\n# 3. 进程树\r\n每一个进程都是被别的进程启动的,或者说是复刻(Fork)的。当系统刚刚启动的时候,有一个非常特别的根进程 init ,它就是是直接被操作系统内核启动的。\r\n\r\n这样一来,这个系统中运行的所有进程集合就构成了一颗以`init`进程为根节点的进程树,所有的其他进程都有一个父进程,也有可能有多个子进程。\r\n\r\n比方说,每次我们在`bash`命令行提示符下执行一个命令的时候,bash 会复刻一个新的进程来执行这个命令,这时这个进程就变成了`bash`的子进程了。\r\n\r\n相似地,当我们看见一个「登录」提示符时,这其实是`login`命令在运行着。如果我们成功的登录了,`login`命令会复刻一个新的进程来执行登录用户选择的`shell`。\r\n\r\n我们可以使用`ps auxf`命令来查看树形结构的进程列表,像下面这样:\r\n\r\n```shell\r\n-+= 00001 root /sbin/launchd\r\n |--= 00085 root /usr/libexec/logd\r\n |--= 00086 root /usr/libexec/UserEventAgent (System)\r\n |--= 00089 root /System/Library/PrivateFrameworks/Uninstall.framework/Resource\r\n |--= 00090 root /System/Library/Frameworks/CoreServices.framework/Versions/A/F\r\n |--= 00091 root /System/Library/PrivateFrameworks/MediaRemote.framework/Suppor\r\n |-+= 00093 root /usr/sbin/systemstats --daemon\r\n | \\--- 00359 root /usr/sbin/systemstats --logger-helper /private/var/db/system\r\n |--= 00095 root /usr/libexec/configd\r\n |--= 00096 root endpointsecurityd\r\n```\r\n\r\n# 4. 进程归属权\r\n每一个进程都归属于某个特定的用户,归属于该用户的进程有权限像该用户直接登录了一样执行所有该用户可以执行的所有命令。\r\n\r\n比方说,假如有一个进程归`shinerio`用户所有,那么这个进程就可以做所有`shinerio`用户能做的事情了:编辑`shinerio`用户`home`目录下的文件,启动一个归属于`shinerio`用户的新进程,等等。\r\n\r\n系统进程比如`init`和`login`归属于`root`用户,而且当一个根进程复刻一个新进程的时候,它可以改变这个子进程的归属。\r\n\r\n所以,当我们登录后, `login`命令会复刻一个新的进程我运行我们的`shell`,但是新的进程是所属于成功登陆的那个用户的。接下来所有的后续命令都会以该用户的名义执行,所启动的进程都归属于他。\r\n\r\n默认情况下,只有 root 进程可以像这样改变归属权。\r\n\r\n# 5. Init System\r\n操作系统内核在初始化进程中所做的最后一件事情就是启动「init system」,也就是执行 `/sbin/init`命令。「init system」有很多种,但它们都有相同的职责:\r\n\r\n1. 控制哪些服务在系统启动时跟随启动\r\n2. 提供可以开启、停止服务的工具,并且给出服务的状态信息总览\r\n3. 提供一个可以编写新的服务的框架\r\n\r\n这里的服务涵盖了从`web`服务器到用来管理登录的系统级服务器在内的所有服务。基本上,一个「init system」的工作就是让所有面向用户(即非内核)的程序和服务运行起来。\r\n\r\n> 例如,Ubuntu和centos都使用`systemd`作为默认的「init system」。根据 Linux 惯例,字母 d 是守护进程(daemon)的缩写。 Systemd这个名字的含义,就是它要**守护整个系统**。\r\n\r\n(1)-(3) 中设计的特定命令和工具会因不同的「init system」而各有不同。`Linux`系统历史上最通用的一个「init system」叫做「System V Init」,它是以极具影响力的`UNIX SYSTEM V`来命名的。在现在`Linux`系统中,同时被`CentOS`、`RedHad`、`Debian`、`Ubuntu`等等主流发行版本所采用的「init system」叫做「systemd init system」。\r\n\r\n有两点需要铭记:\r\n\r\n1. 不同的 Linux 发行版本可以使用不同的「init system」\r\n2. 同一 Linux 发型版本的不同版本号可以使用不同的「init system」\r\n# 8. 终止进程\r\n我们可以使用`kill`命令或`killall`命令来终止一个进程。常用的kill命令如下:\r\n- 1 (HUP):重新加载进程。\r\n- 2 (INT):    中断(同`Ctrl + C`)\r\n- 3 ( QUIT):   退出(同`Ctrl + \\`)\r\n- 9 (KILL):无条件强制杀死一个进程。\r\n- 15 (TERM):正常停止一个进程。\r\n- 18 (CONT):    继续(与STOP相反)\r\n- 19 (STOP):    暂停(同`Ctrl + Z`)\r\n\r\n> 只有第9种信号(SIGKILL)才可以无条件终止进程,其他信号进程都有权利忽略\r\n\r\n默认情况下,`kill`和`killall`命令会发送`TERM`信号给特定的进程。`TERM`信号是一个「优雅」的终止信号,进程收到这个信号时会以合适的方式处理和结束进程。比如,被终止的进程可能想要在终止之前完成当前的任务、或者是清理可能会残留在系统中的临时文件等等。\r\n\r\n如果一个进程有漏洞导致它已经不能响应`TERM`信号了,这种情况下我们就只能发送另一个比较激进的信号了。有两种方法可以发送这个信号:\r\n\r\n1. kill -KILL pid\r\n2. kill -9 pid\r\n\r\n`kill -9`或者`killall -9`指令都是非常激进了,粗略地等同于直接拔掉计算机的电源。像这样来终止进程可能会留下一堆麻烦,只不过如果进程真的不响应了,也没啥别的办法。所以,在使用`kill -9 PID`之前,一定要先尽量尝试使用`kill PID`才是。\r\n\r\n# 9. 进程API\r\n## 9.1. Fork\r\n```c\r\n#include \r\n#include \r\n#include \r\n\r\nint main(int argc, char *argv[]){\r\n printf(\"hello world (pid: %d)\\n\", (int) getpid());\r\n int rc = fork();\r\n if (rc < 0){\r\n fprintf(stderr, \"fork failed\\n\");\r\n exit(1);\r\n } else if (rc == 0){\r\n printf(\"hello, I am child (pid: %d)\\n\", (int) getpid());\r\n } else {\r\n printf(\"hello, I am parent of %d (pid: %d)\\n\", rc, (int) getpid());\r\n }\r\n sleep(10);\r\n return 0;\r\n}\r\n```\r\n1. 输出hello world以及当前进程pid\r\n2. fork()系统调用,创建新进程;新进程与调用进程几乎完全一样,只不过新创建的线程不会从main函数入口开始执行,而是从fork()系统调用直接返回,父进程返回值是子进程的PID,子进程返回的是0;子进程拥有自己独立的地址空间、寄存器、程序计数器等\r\n3. 第二行和第三行的打印顺序是随机的,这取决于CPU在父子进程之间的调度顺序\r\n```shell\r\nhello world (pid: 80471)\r\nhello, I am parent of 80479 (pid: 80471)\r\nhello, I am child (pid: 80479)\r\n# ps aux|grep fork_test|grep -v grep\r\nshinerio 80479 0.0 0.0 4278540 420 ?? S 10:47下午 0:00.00 /Users/shinerio/WorkSpace/c_learn/fork_test\r\nshinerio 80471 0.0 0.0 4277516 760 ?? SX 10:47下午 0:00.00 /Users/shinerio/WorkSpace/c_learn/fork_tes\r\n```\r\n## 9.2. Wait\r\nwait()系统调用可以让父进程等待子进程执行完成。\r\n```c\r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\nvoid print_time(){\r\n time_t now;\r\n time(&now);\r\n struct tm * timeinfo = localtime(&now);\r\n char buffer[80];\r\n strftime(buffer, sizeof(buffer), \"%Y-%m-%d %H:%M:%S\", timeinfo);\r\n printf(\"time: %s\", ctime(&now));\r\n}\r\n\r\nint main(int argc, char *argv[]){\r\n printf(\"hello world (pid: %d)\\n\", (int) getpid());\r\n int rc = fork();\r\n if (rc < 0){\r\n fprintf(stderr, \"fork failed\\n\");\r\n exit(1);\r\n } else if (rc == 0){\r\n print_time();\r\n printf(\"hello, I am child (pid: %d)\\n\", (int) getpid());\r\n } else {\r\n wait(NULL); // waitpid(rc, NULL, 0);\r\n print_time();\r\n printf(\"hello, I am parent of %d (pid: %d)\\n\", rc, (int) getpid());\r\n }\r\n sleep(10);\r\n return 0;\r\n}\r\n```\r\n`wait(NULL)`只会等待任意一个子进程完成,即多个子进程的情况下,父进程只会阻塞到第一个执行完成的子进程。使用`pid_t waitpid(pid_t pid, int *status, int options);`可以明确等待指定pid号的进程执行完成。\r\n- `pid`:指定要等待的子进程的进程 ID。其取值有以下几种情况:\r\n - `pid > 0`:等待进程 ID 为 `pid` 的子进程。\r\n - `pid == 0`:等待与调用进程(父进程)属于同一进程组的任何子进程。\r\n - `pid == -1`:等待任何子进程,作用类似于 `wait(NULL)`。\r\n - `pid < -1`:等待进程组标识符为 `|pid|` 的任何子进程。\r\n- `status`:一个指向整数的指针,用于存储子进程的退出状态信息。如果不关心子进程的退出状态,可以将其设置为 `NULL`。\r\n- `options`:控制 `waitpid()` 的行为,是一个位掩码,可以使用以下标志的组合(使用 `|` 运算符):\r\n - `WNOHANG`:如果没有子进程退出,立即返回,而不是阻塞父进程。\r\n - `WUNTRACED`:除了等待已终止的子进程,还会等待已停止的子进程。\r\n - `WCONTINUED`:等待已停止的子进程被继续运行。\r\n```\r\nhello world (pid: 80921)\r\ntime: Tue Jan 14 23:02:10 2025\r\nhello, I am child (pid: 80926)\r\ntime: Tue Jan 14 23:02:20 2025\r\nhello, I am parent of 80926 (pid: 80921)\r\n```\r\n\r\n## 9.3. Exec\r\nFork()系统调用可以让子进程执行与父进程一样的程序,而exec系列函数可以让子进程与父进程执行不一样的程序。\r\n不同的 `exec` 函数在参数传递和环境变量处理上有不同的方式,根据实际需求选择合适的函数。例如:\r\n- 如果要使用 `PATH` 环境变量查找可执行文件,可以使用 `execlp` 或 `execvp`;\r\n- 如果要明确指定路径,可以使用 `execv` 或 `execve`;\r\n- 如果要指定环境变量,可以使用 `execle` 或 `execve`\r\n\r\nexec系列函数会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据),堆、栈及其他内存空间也会被重新初始化。同时,对exec函数的调用永远不会返回,也就是原进程的后续代码不会执行。\r\n```c\r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\n\r\nint main(int argc, char *argv[]){\r\n int rc = fork();\r\n if (rc < 0){\r\n fprintf(stderr, \"fork failed\\n\");\r\n exit(1);\r\n } else if (rc == 0){\r\n printf(\"I am child (pid: %d)\\n\", (int) getpid());\r\n char *myargs[3];\r\n myargs[0] = strdup(\"wc\");\r\n myargs[1] = strdup(\"fork_test.cpp\");\r\n myargs[2] = NULL;\r\n execvp(myargs[0], myargs);\r\n printf(\"this shouldn't print out\");\r\n } else {\r\n printf(\"I am parent of %d (pid: %d)\\n\", rc, (int) getpid());\r\n }\r\n}\r\n```\r\n输出如下:\r\n```\r\nI am parent of 91679 (pid: 91678)\r\nI am child (pid: 91679)\r\n 24 70 595 fork_test.cpp\r\n```\r\n## 9.4. fork和exec组合\r\nfork和exec都是一个最简单的抽象,fork用于克隆一个进程,而exec用于执行一个新的进程,通过对这两个简单接口的组合可以实现非常强大的功能。如先fork进程,然后做一些环境准备工作,然后再执行exec,最后wait等待子进程执行完成。\r\n\r\n以shell中最简单的重定向举例`wc test.txt > count.txt`\r\n- shell本身也是一个用户程序,它先fork()创建新进程\r\n- 然后关闭标准输出\r\n- 打开文件count.txt\r\n- 然后调用exec来执行wc程序,这样程序输出结果就不是屏幕,而是count.txt文件了。\r\n```c++\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\nint main(int argc, char *argv[]){\r\n int rc = fork();\r\n if (rc < 0){ \r\n fprintf(stderr, \"fork failed\\n\");\r\n exit(1);\r\n } else if (rc == 0){\r\n close(STDOUT_FILENO);\r\n open(\"./fork_test.txt\", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);\r\n char *myargs[3];\r\n myargs[0] = strdup(\"wc\");\r\n myargs[1] = strdup(\"fork_test.cpp\");\r\n myargs[2] = NULL; \r\n execvp(myargs[0], myargs);\r\n } else {\r\n printf(\"I am parent of %d (pid: %d)\\n\", rc, (int) getpid());\r\n }\r\n}\r\n```\r\n以上代码会将`wc fork_test.cpp`的命令输出结果写到fork_test.txt文件中\r\n# 10. 受限执行\r\n受限直接执行是操作系统中一种用于实现进程控制和管理的技术手段\r\n## 10.1. 基本概念\r\n受限直接执行是指在操作系统的管理下,让程序在处理器上直接运行,但对其访问资源和执行操作的权限进行严格限制的一种执行方式。它允许进程在一定的约束条件下,直接使用处理器资源来执行指令,完成任务,但又防止其对系统资源进行非法或不合理的访问和操作,以保证系统的稳定性、安全性和公平性。\r\n### 10.1.1. 实现方式\r\n- **硬件支持**:硬件提供了一些机制来支持受限直接执行。例如,处理器具有不同的特权级别,如[[操作系统地址空间#1. 用户空间与内核空间|用户态和内核态]]。在用户态下运行的进程受到限制,不能直接访问一些关键的硬件资源和执行特权指令,而内核态则具有更高的权限,可以进行各种底层的系统操作。通过这种特权级别的设置,硬件可以确保进程在受限的环境下运行。\r\n- **操作系统管理**:操作系统通过进程控制块(PCB)等数据结构来管理进程的执行。操作系统会为每个进程分配一定的资源,如内存空间、CPU 时间片等,并记录进程的状态信息。在进程执行过程中,操作系统会根据进程的状态和资源分配情况,对进程进行调度和切换,确保每个进程都能在受限的条件下公平地使用系统资源。同时,操作系统还会通过内存管理、文件系统管理等模块,对进程的资源访问进行监控和限制,防止进程越界访问或非法操作。\r\n### 10.1.2. 作用和意义\r\n- **资源隔离与保护**:可以将不同进程的运行环境隔离开来,防止一个进程非法访问或修改其他进程的资源,避免进程之间的相互干扰和破坏,从而保证系统中各个进程的独立性和稳定性。\r\n- **系统安全保障**:限制了进程对系统关键资源和敏感信息的访问,降低了恶意程序或错误程序对系统造成严重破坏的可能性,增强了系统的安全性。\r\n- **公平分配资源**:操作系统可以根据一定的调度算法,为各个进程分配合理的 CPU 时间和其他资源,使各个进程能够在受限的条件下公平地竞争资源,提高系统资源的利用率和整体性能。\r\n# 11. 进程切换\r\n上下文切换时长,对于一个2GHz或3GHz的处理器,上下文切换的时间可以达到亚微秒级别。\r\n![[操作系统地址空间#1.3. 进程切换]]\r\n# 12. 参考链接\r\n1. [Linux 系统里的进程状态](https://liam.page/2020/01/10/the-states-of-processes-on-Linux/)"},{"id":"(一)NAT基本介绍","title":"NAT概览","date":"2022-05-04T00:00:00.000Z","tags":["nat","network"],"readingTime":13,"slug":"一nat基本介绍","description":"1. 背景 地址使用4个字节进行存储,最多能够提供个地址。随着互联网尤其是物联网的发展,全球地址早已不够用,因此人们发明了(网络地址转换)来缓解这个问题。 简单来说,国际互联网组织划分了三个网络地址段作为内部网络本地通信使用,分别是,,。大部分内部机器都使用这些网段中的私有地址,如果它们需要访问公网...","relativePath":"Tech/Network/NAT/(一)NAT基本介绍.md","rawContent":"---\r\ntitle: NAT概览\r\ndate: 2022-05-04\r\ncategories:\r\n- network\r\ntags:\r\n- nat\r\n- network\r\n---\r\n\r\n# 1. 背景\r\n`IPv4`地址使用4个字节进行存储,最多能够提供`2^32`个`IPv4`地址。随着互联网尤其是物联网的发展,全球`IPv4`地址早已不够用,因此人们发明了`NAT`(网络地址转换)来缓解这个问题。\r\n\r\n简单来说,国际互联网组织划分了三个网络地址段作为内部网络本地通信使用,分别是`10.0.0.0/8`,`172.16.0.0/12`,`192.168.0.0/16`。大部分内部机器都使用这些网段中的私有`IP`地址,如果它们需要访问公网服务,那么则需要经过`NAT`。\r\n\r\n- **出向流量**:需要经过一台`NAT`设备,它会对流量进行`SNAT`(源地址替换),将私网地址和端口转换成`NAT`设备的公网地址和端口,然后再将包发出去;\r\n- **回程流量**:到达`NAT`设备后进行相反的转换`DNAT`(目的地址替换),然后再转发给客户端。\r\n\r\n\r\n\r\n# 2. 三四层NAT\r\n`NAT`(Network address translation)即网络地址转换,工作在`OSI`模型的三层或四层(`PNAT`),用于修改`IP`数据包中的`IP`地址和端口。当内网的一些主机本来已经分配到了`local ip`地址,但又想和`Internet`的主机通信时,可使用`NAT`技术。\r\n\r\n为了更好地表述各种NAT映射方式的区别,我们首先统一下相关术语。\r\n- 内网主机IP:`InternalIp`\r\n- 内网主机端口:`InternalPort`\r\n- `NAT`后的外网主机`IP`:`TransitIp`\r\n- NAT后的外网主机端口:`TransitPort`\r\n- 外网主机`IP`:`ExternalIp`\r\n- 外网主机端口:`ExternalPort`\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/NAT%E5%9F%BA%E7%A1%80%E6%A8%A1%E5%9E%8B.png)\r\n\r\n在最常见的家庭网络中,我们大部分的的设备(手机、电脑、具备联网功能智能家居)都处于internal network环境中,具有一个`192.168.x.x`的地址,而这个地址是一个私网地址,是不能被公网设备识别和路由的。日常访问的百度、淘宝等都可以认为是工作在external network环境中,具备一个或一组代表身份和位置信息的公网IP地址。为了实现内网设备访问公网服务,就必须在设备接入公网前经历一次`NAT`,将一个公网的IP用作`transitIP`。在运营商给你分配了公网ip的情况下,你家的路由器就是`firewall`,承担了`NAT`的功能。这种情况下的路由器一般有两个地址,一个是`192.168.x.1`的ip地址用作内网设备的默认网关,另一个则是用作NAT的公网IP地址,此时路由器就像一座桥一样,连接了两个本不能互通的网络环境。\r\n\r\n## 2.1. 按NAT使用场景\r\n从`NAT`的使用场景来看,`NAT`可以分为`SNAT`、`DNAT`和`FULLNAT`\r\n- SNAT,替换源地址,SNAT一般可以分为两种\r\n\t1. 地址池方式,适用于有大量内网主机需要访问外网主机的场景,使用地址池中的随机IP替换源地址\r\n\t2. 出接口方式,内网主机直接使用外网接口的IP地址访问外网,适用于外网接口IP是动态获取的场景。\r\n- DNAT,替换目的地址。\r\n- FULLNAT,源地址和目的地址同时替换,使用FULLNAT可以简化内部主机的设置,避免配置网关或路由。\r\n\r\n## 2.2. 按映射方式划分\r\n从`NAT`的映射方式来看,`NAT`可以分为`Basic NAT`和`PNAT`:\r\n- **Basic NAT**,只转化`IP`,`IP`不复用,不映射端口,内外部`IP`地址一比一映射,工作在`OSI`模型的第三层。\r\n- **PNAT**,除了转化IP,还复用`IP`,做端口映射,可以用于多个内部地址映射到少量(甚至一个)外部地址,工作在`OSI`模型的第四层。\r\n\r\n## 2.3. 按生命周期划分\r\n从`NAT`的生命周期来看,可以分为静态`NAT`和动态`NAT`:\r\n- **静态NAT**,将内部网络中的每个主机都永久映射成外部网络中的某个地址。\r\n- **动态NAT**,在外部网络中定义了一个或多个合法地址池,采用动态分配会话的方法将内部网络地址映射为外部网络地址。同时,分配的会话存在过期时间,过期后自动回收。\r\n\r\n## 2.4. 按形态划分\r\n从`NAT`的形态上来看,可以分为完全锥型`NAT`、地址受限锥型`NAT`、端口受限锥型`NAT`和对称型`NAT`。\r\n- **完全锥型NAT**,完全锥形`NAT`只与`InternalIp`、`InternalPort`有关,只要`InternalIp`和`InternalPort`不变,都会分配同一个`TransitIp`和`TransitPort`。我们把`InternalIp:InternalPort`到`TransitIp:TransitPort`的这个映射关系称之为[[(二)NAT会话|会话]]。一旦这个会话建立起来,任意外网的主机都可以通过访问映射后的`TransitIp`和`TransitPort`,实现**主动**访问位于内网的主机设备功能。\r\n- **地址受限锥型NAT**,地址受限锥型`NAT`与完全锥型`NAT`的区别在于,会话除了维护`IP`和端口的映射关系外,还维护了内网主机主动访问的外网主机`IP`列表,不在列表中的主机无法通过访问内映射后的`TransitIp`和`TransitPort`主动访问内网主机。因此,要想外部主机能够主动向该内部主机发起通信,必须先由该内部主机向这个外部发起一次通信,但是不必担心`ExternalPort`是否与内网主机请求的端口号是否一致。比如内网使用`TransitIp1:TransitPort1`访问外网`ExternalIp1:ExternalPort1`,完全锥型可以实现外网其他主机`ExternalIp2:ExternalPort2`访问`TransitIp1:TransitPort1`,但是地址受限型只能使用源地址为`ExternalIp1`,端口任意的方式来访问`TransitIp1:TransitPort1`。\r\n- **端口受限锥型NAT**,端口受限锥型`NAT`与地址受限锥型`NAT`的区别在于,会话额外保存了外网主机的端口信息。因此外网主机主动访问内网主机,除了有`ExternalIp`的限制外,还有`ExternalPort`的限制。比如内网使用`TransitIp1:TransitPort1`访问外网`ExternalIp1:ExternalPort1`,地址受限型可以使用`ExternalIp1:ExternalPort2`访问`TransitIp1:TransitPort1`,但是端口受限型只能使用`ExternalIp1:ExternalPort1`访问`TransitIp1:TransitPort1`。\r\n- **对称NAT**,从同一个内网`InternalIp`和`InternalPort`发送到同一个`ExternalIp`和`ExternalPort`的请求都会被映射到同一个`TransitIp`和`TransitPort`。但四元组`(InternalIp,InternalPort,ExternalIp,ExternalPort)`只要有一个发生变化都会使用不同的映射。对称型NAT下,只能由内网主机访问外网主机,外网主机是不允许主动访问内网主机的。\r\n \r\n| NAT类型 | 说明 |\r\n| --------- | ---------------------------------------------- |\r\n| 完全锥型NAT | 一旦会话生成后,任何公网主机都可与之通讯。双方都可以主动发起连接。 |\r\n| 地址受限锥型NAT | 只有内网主动连接的公网主机可与之通讯。在内网主机发起连接后,被访问的公网主机可通过任意端口主动与内网主机通讯。 |\r\n| 端口受限锥型NAT | 只有内网主动连接的公网主机可与之通讯。在内网主机发起连接后,被访问的公网只能通过固定的端口与之进行通讯。 |\r\n| 对称型NAT | 根据四元组创建NAT映射,四元组中的任何一项发生变化均导致NAT映射的更换。此时双方一对一映射,因此称之为对称NAT。对称型NAT下,只能由内网主机访问外网主机,外网主机是不允许主动访问内网主机的。 |\r\n\r\n> 锥型NAT的通信双方是对等的,外网主机不管是换`IP`还是端口,感知到的都是对端的同一个`IP`和端口在提供某个特定服务,双方都可以主动发起通信;而对称型NAT违反了协议双方对等的原则,外网主机是无法主动访问内网主机的,换个外网地址或端口,NAT后的地址和端口就会变化。\r\n\r\n# 3. 七层NAT\r\n普通`NAT`实现了对`UDP`或`TCP`报文头中的的`IP`地址及端口转换的功能,但对七层应用层数据载荷中的字段无能为力。在许多应用层协议中,有很多协议都包含多通道的信息,比如多媒体协议(H.323、SIP等)、FTP、SQLNET等,这种多通道的应用需要首先在控制通道中对后续数据通道的`IP`和端口进行协商,然后根据协商结果创建多个数据通道连接。控制信道的报文中,`TCP/UDP`除了传输层头中携带`IP`地址信息外,报文的有效载荷中也带有地址或者端口信息,这些内容不能被`NAT`进行有效的转换,就可能导致未知问题。\r\n\r\n而`NAT ALG`(Application Level Gateway,应用层网关)技术能对多通道协议进行应用层报文信息的解析和地址转换,将载荷中需要进行地址转换的`IP`地址和端口或者需特殊处理的字段进行相应的转换和处理,从而保证应用层通信的正确性。`ALG`需要识别并适配每一种应用层协议,不同应用协议的载荷中IP地址和端口的位置不同,因此具有很大的局限性,幸运的是ALG支持了我们常用的大多数协议,包括:FTP、H.323(包括RAS、H.225、H.245)、SIP、DNS、ILS、MSN/QQ、NBT、RTSP、SQLNET、TFTP等。\r\n\r\n# 4. NAT应用场景\r\n- 多个内网主机共享公网`IP`,节省`IP`资源\r\n- 作为内网流量统一出口,共享公网带宽\r\n- 隐藏内网主机真实地址,避免直接暴露,可以抵挡端口扫描之类的攻击\r\n- 工作在NAT模式下的lvs四层负载均衡 \r\n\r\n# 5. NAT局限性\r\n- `NAT`下的网络被分为外网和内网两部分,`NAT`设备一般以网关的形式作为私网到公网的路由出口,双向流量都要经过`NAT`设备,容易成为性能瓶颈\r\n- 由于`NAT`将内部网络信息进行了隐藏和转换,`NAT`下的设备无法进行对等网络传输(需要`NAT`穿透)\r\n- `NAT`不能实现对通信双方的全透明,因为上层协议可能在传输的数据包中携带`IP`和端口信息(需要`NAT ALG`)\r\n- 应用层需保持`UDP`会话连接,而由于`NAT`资源有限,所以`UDP`的会话会很快被回收(以便端口重用)。由于UDP是无连接的,`UDP`层应用需要在无数据传输、但需要保持连接时通过`heartbeat`的方式保持会话不过期。\r\n"},{"id":"NAT Overview","title":"NAT Overview","date":"2022-05-04T00:00:00.000Z","tags":["NAT"],"readingTime":33,"slug":"nat-overview","description":"1. What is NAT? 1.1. NAT(Level 4) NAT(Network address translation)即网络地址转换,工作在OSI模型的三层或四层(PNAT),用于修改IP数据包中的IP地址和端口。当在专用网内部的一些主机本来已经分配到了local ip地址,但又想和I...","relativePath":"Tech/Network/NAT/NAT Overview.md","rawContent":"---\r\ntitle: NAT Overview\r\ndate: 2022-05-04\r\ncategories:\r\n- 计算机网络\r\ntags:\r\n- NAT\r\n---\r\n\r\n# 1. What is NAT?\r\n\r\n## 1.1. NAT(Level 4)\r\n\r\nNAT(Network address translation)即网络地址转换,工作在OSI模型的三层或四层(PNAT),用于修改IP数据包中的IP地址和端口。当在专用网内部的一些主机本来已经分配到了local ip地址,但又想和Internet的主机通信时,可使用NAT方法。\r\n\r\n\r\n\r\n从NAT的映射方式来看,NAT可以分为Basic NAT和PNAT:\r\n- **Basic NAT**,只转化IP,IP不复用,不映射端口。\r\n- **PNAT**,除了转化IP,还复用IP,做端口映射,可以用于多个内部地址映射到少量(甚至一个)外部地址。\r\n \r\n从NAT的生命周期来看,可以分为静态NAT和动态NAT:\r\n- **静态NAT**,将内部网络中的每个主机都永久映射成外部网络中的某个地址。\r\n- **动态NAT**,在外部网络中定义了一个或多个合法地址池,采用动态分配的方法将内部网络映射为外网网络。会话存在过期时间,过期后自动回收。\r\n\r\n从NAT的形态上来看,可以分为锥型NAT、对称型NAT。\r\n\r\n- **锥型NAT**,锥形NAT只与源地址、源端口有关,只要源地址和源端口不变,都会分配同一个外网地址和端口。因此外网主机可以通过访问映射后的公网地址和端口,实现访问位于内网的主机设备功能。\r\n- **对称NAT**,从同一个内网IP和端口发送到同一个目的IP和端口的请求都会被映射到同一个外网IP和端口。但四元组(SIP,Sport, DIP, Dport)只要有一个发生变化都会使用不同的映射。\r\n\r\n> 锥型NAT的通信双方是对等的,外网主机不管是换IP还是端口,感知到的都是对端的同一个IP和端口在提供某个特定服务,而对称型NAT违反了协议双方对等的原则,外网主机是无法主动访问内网主机的,换个外网地址或端口,NAT后的地址和端口就会变化。对称型NAT下,外网主机主动发起tcp连接,源端口是随机的(对于内网的NAT来说,即目的端口是不固定的),那么对称型NAT分配的端口也是随机的,当然这个随机端口也无法被NAT回内网。\r\n\r\n![enter description here](https://raw.githubusercontent.com/shinerio/shinerio.github.io/blog-images/小书匠/1652541034741.png)\r\n\r\n> 锥型NAT有两种特殊形态,即地址受限型NAT和端口受限型NAT。地址受限型NAT校验目的IP(外网的IP),因此地址受限型NAT不能主动连接内网中的主机地址,连接必须由内网地址发起。端口受限型校验目的端口和ip,地址受限型NAT只有内网主机与之通讯后,才可以进行通讯,不用担心端口号是否与内网请求的端口相同,但是端口受限型NAT也增加了端口限制。比如内网使用ip1:port1访问外网ip2:port2,地址受限型可以使用ip2:port3访问ip1:port1,但是端口受限型只能使用ip2:port2访问ip1:port1。\r\n\r\n| NAT类型 | 说明 |\r\n| --------------- | --------------------------------------------------------------------------------------------------------------- |\r\n| 全锥型NAT | 任何公网主机都可与之通讯。双方都可以主动发起。 |\r\n| 地址受限锥型NAT | 只有内网主动连接的公网主机可与之通讯,必须内网主机发起。且此公网主机可通过任意端口与内网主机通讯。 |\r\n| 端口受限锥型NAT | 只有内网主动连接的公网主机的连接可与之通讯,必须内网主机发起。且此公网只能通过固定的端口与之进行通讯。 |\r\n| 对称型NAT | 根据四元组创建NAT映射,四元组中的任何一项发生变化均导致NAT映射的更换。此形状双方一对一映射,因此被称之为对称NAT |\r\n\r\n## 1.2. NAT(Level 7)\r\n\r\n普通NAT实现了对UDP或TCP报文头中的的IP地址及端口转换功能,但对应用层数据载荷中的字段无能为力,在许多应用层协议中,比如多媒体协议(H.323、SIP等)、FTP、SQLNET等,TCP/UDP载荷中带有地址或者端口信息,这些内容不能被NAT进行有效的转换,就可能导致问题。而NAT ALG(Application Level Gateway,应用层网关)技术能对多通道协议进行应用层报文信息的解析和地址转换,将载荷中需要进行地址转换的IP地址和端口或者需特殊处理的字段进行相应的转换和处理,从而保证应用层通信的正确性。ALG需要识别并适配每一种应用协议,不同应用协议的载荷中IP地址和端口的位置不同,因此具有很大的局限性,幸运的是ALG支持了我们常用的大多数协议。\r\n\r\n# 2. Why we need NAT and why we hate NAT?\r\n\r\n## 2.1. advantage\r\n\r\n- 多个内网主机共享公网IP,节省IP资源\r\n- 作为内网流量统一出口,共享带宽\r\n- 隐藏内网主机真实地址,避免直接暴露,可以抵挡port scan之类的攻击\r\n- 工作在NAT模式下的lvs四层负载均衡\r\n\r\n# 3. weakness\r\n- NAT下的网络被分为外网和内网两部分,以网关的形式作为私网到公网的路由出口,双向流量都要经过NAT设备,容易成为性能瓶颈\r\n- 由于NAT将内部网络信息进行了隐藏和转换,NAT下的设备无法进行对等网络传输(需要穿透NAT)\r\n- NAT不能实现对通信双方的全透明,因为上层协议可能在传输的数据包中携带ip和port信息(需要七层ALG)\r\n- 应用层需保持UDP会话连接。由于NAT资源有限,所以UDP的会话会很快被回收(以便端口重用)。由于UDP是无连接的,因此UDP层应用需要在无数据传输、但需要保持连接时通过heartbeat的方式保持会话不过期。\r\n\r\n# 4. How to NAT in router?\r\n\r\n以下配置了子网10.10.10.0/24的前32个地址和子网10.10.20.0/24的前32个地址可以通过NAT访问外网,内部网络中可能有其他设备具有其他地址,但这些地址不会被转换。\r\n\r\n![enter description here](https://raw.githubusercontent.com/shinerio/shinerio.github.io/blog-images/小书匠/1652546543650.png)\r\n\r\n```shell\r\ninterface ethernet 0\r\n ip address 10.10.10.1 255.255.255.0\r\n ip nat inside\r\n\r\n!--- Defines Ethernet 0 with an IP address and as a NAT inside interface.\r\n\r\ninterface ethernet 1\r\n ip address 10.10.20.1 255.255.255.0\r\n ip nat inside\r\n\r\n!--- Defines Ethernet 1 with an IP address and as a NAT inside interface.\r\n\r\ninterface serial 0\r\n ip address 172.16.10.64 255.255.255.0\r\n ip nat outside\r\n\r\n!--- Defines serial 0 with an IP address and as a NAT outside interface.\r\n\r\nip nat pool no-overload 172.16.10.1 172.16.10.63 prefix 24\r\n !\r\n\r\n!--- Defines a NAT pool named no-overload with a range of addresses !--- 172.16.10.1 - 172.16.10.63\r\n\r\nip nat inside source list 7 pool no-overload \r\n !\r\n !\r\n\r\n!--- Indicates that any packets received on the inside interface that !--- are permitted by access-list 7 has !--- the source address translated to an address out of the !--- NAT pool \"no-overload\".\r\n\r\naccess-list 7 permit 10.10.10.0 0.0.0.31\r\naccess-list 7 permit 10.10.20.0 0.0.0.31\r\n\r\n!--- Access-list 7 permits packets with source addresses ranging from !--- 10.10.10.0 through 10.10.10.31 and 10.10.20.0 through 10.10.20.31.\r\n```\r\n\r\n# 5. How to NAT in Linux?\r\n\r\n在物理网络中,NAT功能一般由路由器或防火墙之类的设备来承载,而`Iptables`则是linux提供的用户态命令行工具(实际是工作的是在内核态的`netfilter`),可以通过在nat表中增加规则,实现在`PREROUTING`和`POSTROUTING`链上的`DNAT`和`SNAT`功能。要使用Linux提供的nat功能,首先需要确认主机支持转发,可通过以下方式确认:\r\n\r\n```shell\r\ncat /proc/sys/net/ipv4/ip_forward\r\n```\r\n回显结果:1为开启,0为关闭,默认为0。\r\n\r\n> 如未打开,可通过修改`/etc/sysctl.conf`文件,配置`net.ipv4.ip_forward = 1`后执行`sysctl -p /etc/sysctl.conf`启用转发功能。\r\n\r\n\r\n## 5.1. SNAT\r\n\r\nSNAT是为了让内网主机访问外网主机,将出方向报文的源地址进行替换,因此可以在`POSTROUTING`链上添加如下规则。\r\n\r\n```shell\r\n# 将192.168.1.0/24网段的源地址替换为100.32.1.100\r\niptables -t nat -A POSTROUTING -o eth0 -s 192.168.1.0/24 -j SNAT --to 100.32.1.100\r\n```\r\n\r\n## 5.2. DNAT\r\n\r\nDNAT是为了让外网主机访问内网主机,将入方向报文的目的地址进行替换,因此可以在`PREROUTING`链上添加如下规则。\r\n\r\n```shell\r\n# 全端口映射\r\niptables -t nat -A PREROUTING -i eth0 -d 100.32.1.101 -j DNAT --to 192.168.1.101\r\n# 单端口映射\r\niptables -t nat -A PREROUTING -i eth0 -p tcp -d 100.32.1.101 --dport 80 -j DNAT --to 192.168.1.101:80\r\n# 范围端口映射\r\niptables -t nat -A PREROUTING -i eth0 -p tcp -d 100.32.1.101 --dport 10000:20000 -j DNAT --to 192.168.1.101:30000-40000\r\n```\r\n\r\n> iptables提供的NAT功能并不会对范围大小、端口冲突功能进行检查。iptables的工作原理为在一条链上从前到后匹配,前面的规则会覆盖后面的规则。 \r\n\r\n## 5.3. Conntrack\r\n\r\n上面我们提到了使用`iptables`实现SNAT功能,而SNAT存在多个内网主机通过分配端口复用同一个公网IP的情况,因此需要有个状态保持机制来记录这个会话,这个功能模块就是`Conntrack`。\r\n\r\n\r\n![enter description here](https://raw.githubusercontent.com/shinerio/shinerio.github.io/blog-images/小书匠/1651666903738.png)\r\n连接跟踪(Conntrack),顾名思义,就是跟踪(并记录)连接的状态。例如,上图是一台IP地址为`10.1.1.2`的Linux机器,我们能看到这台机器上有三条连接:\r\n\r\n- 内部访问外部HTTP服务的连接(目的端口 80)\r\n- 外部访问内部FTP服务的连接(目的端口 21)\r\n- 内部访问外部DNS 服务的连接(目的端口 53)\r\n \r\n连接跟踪所做的事情就是发现并跟踪这些连接的状态,具体包括:\r\n\r\n- 从数据包中提取元组(tuple)信息,辨别数据流(flow)和对应的连接(connection)\r\n- 为所有连接维护一个状态数据库(conntrack table),例如连接的创建时间、发送包数、发送字节数等等\r\n- 回收过期的连接(GC)\r\n- 为更上层的功能(例如 NAT)提供服务\r\n\r\n`Conntrack`在Linux中是通过`NetFilter`实现,`Netfilter` 是Linux内核中一个对数据包进行控制、修改和过滤(manipulation and filtering)的框架。它在内核协议栈中设置了若干hook点,以此对数据包进行拦截、过滤或其他处理。说地更直白一些,hook机制就是在数据包的必经之路上设置若干检测点,所有到达这些检测点的包都必须接受检测,根据检测的结果决定:\r\n\r\n- 放行:不对包进行任何修改,退出检测逻辑,继续后面正常的包处理\r\n- 修改:修改IP地址或端口进行NAT,然后将包放回正常的包处理逻辑\r\n- 丢弃:安全策略或防火墙功能\r\n \r\n>连接跟踪模块只是完成连接信息的采集和录入功能,并不会修改或丢弃数据包,后者是其他模块(例如 NAT)基于`Netfilter hook`完成的。\r\n\r\n# 6. How to NAT in cloud?\r\n\r\n“NAT网关”作为云服务的一种,面向公有云客户服务,工作在overlay网络,其一般基于通用服务器(一般为Linux服务器)实现。在How to NAT in Linux一节中,我们已经知道,可以通过`netfilter`来实现,但这种方式只能靠单机工作,存在着**性能瓶颈**和**单点故障**的问题。为了提高性能和可靠性,我们可以从两方面入手,分别是`scale up`和`scale out`。同时,公有云服务面向了很多客户,因此云上NAT的功能对我们还有一个特殊的要求,实现多租户配置隔离。\r\n\r\n## 6.1. Scale up\r\n\r\n### 6.1.1. Better Hardware\r\nScale up最简单的方式就是增强硬件,这种方式只能在一定程度上提高性能,但受限于硬件能力,还是存在性能上限,且存在单点故障的缺陷。\r\n\r\n- bigger cpu core\r\n- greater bandwidth NIC\r\n\r\n### 6.1.2. Architecture Refactor\r\n\r\nScale up的另外一种方式就是重构,通过这种方式我们可以提高硬件的利用效率,从而增强我们的转发性能。\r\n\r\n网络设备(路由器、交换机等)需要在瞬间进行大量的报文收发,因此在传统的网络设备上,往往能够看到专门的NP(Network Process)处理器,有的用FPGA,有的用ASIC。这些专用器件通过内置的硬件电路(或通过编程形成的硬件电路)高效转发报文,只有需要对报文进行深度处理的时候才需要CPU干涉。\r\n\r\n但在公有云、NFV等应用场景下,基础设施以CPU为运算核心,往往不具备专用的NP处理器,操作系统也以通用Linux为主,软件包的处理通常会经过用户态和内核态之间的切换。在虚拟化环境下,路径则会更长,需要分别经过HostOs和GuestOs两层用户态和内核态的切换。基于Linux服务器的报文转发,主要存在以下问题:\r\n\r\n1. 传统的收发报文方式都必须采用硬中断来做通讯,每次硬中断大约消耗100微秒,这还不算因为终止上下文所带来的Cache Miss。\r\n2. 数据必须从内核态用户态之间切换拷贝带来大量CPU消耗,全局锁竞争。\r\n3. 收发包都有系统调用的开销。\r\n4. 内核工作在多核上,为可全局一致,即使采用Lock Free,也避免不了锁总线、内存屏障带来的性能损耗。\r\n5. 从网卡到业务进程,经过的路径太长,有些其实未必要的,例如netfilter框架,这些都带来一定的消耗,而且容易Cache Miss。\r\n\r\n为了提升在通用服务器的数据包处理效能,Intel推出了服务于IA(Intel Architecture)系统的DPDK(Data Plane Development Kit)技术。DPDK应用程序运行在操作系统的User Space,利用自身提供的数据面库进行收发包处理,绕过了Linux内核态协议栈,以提升报文处理效率。DPDK的UIO驱动屏蔽了硬件发出中断,然后在用户态采用主动轮询的方式,这种模式被称为[PMD](http://doc.dpdk.org/guides/prog_guide/poll_mode_drv.html)(Poll Mode Driver)。\r\n\r\n![enter description here](https://raw.githubusercontent.com/shinerio/shinerio.github.io/blog-images/小书匠/1652454183396.png)\r\n\r\n左边是原来的方式数据从 网卡 -> 驱动 -> 协议栈 -> Socket接口 -> 业务\r\n\r\n右边是DPDK的方式,基于UIO(Userspace I/O)旁路数据。数据从 网卡 -> DPDK轮询模式-> DPDK基础库 -> 业务\r\n\r\n用户态的好处是易用开发和维护,灵活性好。并且Crash也不影响内核运行,鲁棒性强。\r\n\r\n## 6.2. Scale out\r\n\r\nScale up的本质还是在提高单机的性能,存在理论性能上限和单点故障问题。云计算的本质就是池化,实现网关的动态弹性扩容才是根本解决方案。通过Scale out的方式可以无限扩容,提高性能,且集群化后,可以避免单点故障,提高可靠性。\r\n\r\n由于NAT网关是有状态的网关,因此无法直接横向扩容,每个节点不能单独管理会话,否则很容易出现冲突。因此Aws提出了一种三层架构形态的网络,即`HyperPlane`。`HyperPlane`将网络分为以下三种类型:\r\n\r\n- TOP: 无状态的转发,一旦网络连接建立后,转发只会在TOP层完成,可以无限横向扩容。\r\n- FLOW MASTER:用来记录网络连接信息,FLOW MASTER是无状态通用的。\r\n- DECIDER:具体实现网络功能逻辑,不同网关实现不同。对于NAT来说,就是实现SNAT和DNAT规则的转换。\r\n\r\n![enter description here](https://raw.githubusercontent.com/shinerio/shinerio.github.io/blog-images/小书匠/1652454138657.png)\r\n\r\n> 这里的每个节点都使用的是池化的ECS,具有很强的弹性扩缩能力。\r\n\r\n- TOP节点成为了无状态的网关,可以理论无限扩容,大大提高了整个网关平台的转发能力\r\n- Flow Master对于所有网关形态来说,不同的五元组都可以交由不同的Flow Master管理链接状态。FLow Master可以做到横向扩容,基于五元组hash,没有会话状态可以向Decider申请。\r\n- Decider不同与Flow Master,对于有状态的网关,不同的实例可以由不同的Decider管理,但同一个实例必须由同一个Decider管理。Decider横向扩容需要考虑hash策略变化带来的会话同步问题。\r\n- Flow Master和Decider存有会话信息,通过绕行两个节点形成主备,消除单点故障问题。\r\n\r\n## 6.3. Tenant Isolation\r\n\r\n这个就比较简单,云上数据包一般都会使用VXLAN协议进行过传递,通过对VXLAN报文中的VNI进行解析,将SNAT规则和DNAT规则与VNI进行绑定,即可实现不同租户之间的规则隔离。\r\n\r\n# 7. NAT Traversal\r\n\r\n目前运营商提供的光猫上网服务和绝大多数的路由器都是锥型NAT,锥型NAT可以实现NAT穿越,从而实现P2P。\r\n\r\n## 7.1. 锥型NAT\r\n\r\n处于不同内网的主机A和主机B,各自先连接服务器,从而在各自NAT设备上打开了一个“孔”,服务器收到主机A和主机B的连接后,知道A与B的公网地址和NAT分配给它们的端口号,然后把这些NAT地址与端口号告诉A与B,由于在锥型NAT的特点,A和B给服务器所打开的“孔”,能给别的任何的主机使用。故A与B可连接对方的公网地址和端口直接进行通信。服务器在这里充当“介绍人”,告诉A与B对方的地址和端口号。\r\n\r\n## 7.2. 地址受限锥型NAT\r\n\r\nA和B还是要先连接服务器,服务器发送A和B的地址和端口信息给A和B,但由于受限制锥形NAT的特点,他们所打开的“孔”,只能与服务器通信。要使他们可以直接通信,解决办法如下:\r\n\r\n假如主机A开始发送一个UDP信息到主机B的公网地址上,与此同时,它又通过服务器中转发送了一个邀请信息给主机B,请求主机B也给主机A发送一个UDP信息到主机A的公网地址上。这时主机A向主机B的公网IP发送的信息导致NAT A打开一个处于主机A的和主机B之间的会话,与此同时,NAT B也打开了一个处于主机B和主机A的会话。一旦这个新的UDP会话各自向对方打开了,主机A和主机B之间就可以直接通信了。\r\n\r\n\r\n## 7.3. 端口受限锥型( Port Restricted Cone)NAT\r\n\r\n同[地址受限锥形NAT](#地址受限锥形NAT)\r\n\r\n## 7.4. 对称型(Symmetric)NAT\r\n\r\n对称型NAT,对于不同的四元组,它都会分配不同的端口号,所以进行打孔比较困难,但也可以进行端口预测打孔,不过不能保证成功。通常,对称NAT分配端口有两种策略,一种是按顺序增加,一种是随机分配。如果这里对称NAT使用顺序增加策略,那么,ClientB将两次被分配的外网地址和端口发送给Server后,Server就可以通知ClientA在这个端口范围内猜测刚才ClientB发送给它的socket中被NAT映射后的外网地址和端口,ClientA很有可能在孔有效期内成功猜测到端口号,从而和ClientB成功通信。\r\n\r\n此外对称型NAT可以使用第三方服务器作为中继者,ClientA和ClientB之间的流量都通过中继服务器C中转,但是中继服务器容易成为性能瓶颈。\r\n\r\n> 1. 以上的NAT穿透,都是对PNAT来进行穿透,主要是针对UDP协议,语音视频通信是用UDP传输的。Basic NAT不修改经过的数据包的端口号,它们可以看作是完全锥形NAT的精简版本,即Basic NAT也可以被穿透。NAT设备将在一定时间后关闭UDP的一个映射,所以为了保持与服务器能够一直通信,服务器或客户端必须要周期性地发送UDP包,保持映射不被关闭。\r\n\r\n> 2. 以上的NAT穿越,都是基于UDP的NAT穿越。UDP的socket允许多个socket绑定到同一个本地端口,而TCP的socket则不允许。想象一下,如果Client A使用IPA:2000端口和服务器通信,那么Client主机已经使用IPA:2000这个socket和服务器建立了一条TCP流,自然也无法再使用IPA:2000端口和client B建立一条TCP流,而UDP是可以做到的。\r\n\r\n## 7.5. NAT-DDNS穿透\r\n\r\nNAT-DDNS是将用户的动态IP地址映射到一个固定的域名上,用户每次连接网络的时候客户端程序就会通过信息传递把该主机的动态IP地址传送给位于服务商主机上的服务器程序,服务项目器程序负责提供DNS服务并实现动态域名解析。DDNS的主要作用就是捕获用户每次变化的IP地址,然后将其与域名相对应,这样其他上网用户就可以通过域名来与用户交流了。可以通过NAT-DDNS穿透来实现家用主机面向互联网提供服务。\r\n\r\n## 7.6. TCP打洞\r\n\r\n### 7.6.1. 套接字和TCP端口的重用\r\n\r\n实现基于TCP协议的p2p\"打洞\"过程中,最主要的问题不是来自于TCP协议,而是来自于来自于应用程序的API接口。这是由于标准的伯克利(Berkeley)套接字的API是围绕着构建客户端/服务器程序而设计的,API允许TCP流套接字通过调用`connect()`函数来建立向外的连接,或者通过`listen()`和`accept()`函数接受来自外部的连接,但是,API不提供类似UDP那样的,同一个端口既可以向外连接,又能够接受来自外部的连接。而且更糟的是,TCP的套接字通常仅允许建立1对1的响应,即应用程序在将一个套接字绑定到本地的一个端口以后,任何试图将第二个套接字绑定到该端口的操作都会失败。\r\n\r\n为了让TCP\"打洞\"能够顺利工作,我们需要使用一个本地的TCP端口来监听来自外部的TCP连接,同时建立多个向外的TCP连接。幸运的是,所有的主流操作系统都能够支持特殊的TCP套接字参数,通常叫做`SO_REUSEADDR`,该参数允许应用程序将多个套接字绑定到本地的一个端口(只要所有要绑定的套接字都设置了SO_REUSEADDR参数即可)。BSD系统引入了SO_REUSEPORT参数,该参数用于区分端口重用还是地址重用,在这样的系统里面,上述所有的参数必须都设置才行。\r\n\r\n### 7.6.2. 打开p2p的TCP流\r\n\r\n假定客户端A希望建立与B的TCP连接。我们像通常一样假定A和B已经与公网上的已知服务器、建立了TCP连接。服务器记录下来每个连入的客户端的公网地址和端口,如同为UDP服务的时候一样。从协议层来看,TCP\"打洞\"与UDP\"打洞\"是几乎完全相同的过程。\r\n\r\n1、客户端A使用其与服务器的连接向服务器发送请求,要求服务器协助其连接客户端B。\r\n2、服务器将B的公网IP和端口返回给A,同时,服务器将A的公网地址和端口发送给B。\r\n3、客户端A和B使用连接服务器的端口异步地发起向对方的公网地址和端口的TCP连接,同时监听各自的本地TCP端口是否有外部的连接。\r\n4、A和B开始等待向外的连接是否成功,检查是否有新连接进入。如果向外的连接由于某种网络错误而失败,如:\"连接被重置\"或者\"节点无法访问\",客户端只需要延迟一小段时间(例如延迟一秒钟),然后重新发起连接即可,延迟的时间和重复连接的次数可以由应用程序编写者来确定。A发出SYN报文到达B的NAT设备,B的NAT设备如果是全锥型NAT,则连接直接建立,否则B的NAT设备丢弃该报,此时B的SYN报文到达A端NAT设备,而A端NAT设备由于看到了A主动访问B的流,因此将SYN报文NAT后转给了A,A的`listen()`函数生效,连接建立成功。\r\n6、TCP连接建立起来以后,客户端之间应该开始鉴权操作,确保目前联入的连接就是所希望的连接。如果鉴权失败,客户端将关闭连接,并且继续等待新的连接接入。客户端通常采用\"先入为主\"的策略,只接受第一个通过鉴权操作的客户端,然后将进入p2p通信过程不再继续等待是否有新的连接接入。\r\n\r\n# 8. Other Tech\r\n\r\n## 8.1. 会话结构\r\n\r\n首先定义几个概念:\r\n\r\n- CIP,内网主机的IP\r\n- Cport,内网主机的port\r\n- DIP,外网主机的IP\r\n- Dport,外网主机的port\r\n- TIP,NAT后的IP\r\n- Tport,NAT后的port\r\n### 8.1.1. 完全锥型NAT\r\n\r\n- Up Key,内部四元组(CIP、Cport、Protocol)\r\n- Down Key,外部四元组(TIP、Tport、Protocol)\r\n- Session,Up key + Down key + Start Time + ExpireTime\r\n\r\n上行,内网主机访问外网只要CIP、Cport不变,一定会映射到TIP、Tport;下行,外网主机访问TIP、Tport总能NAT会内网的CIp和Cport;在Key中添加protocol可以实现TCP和UDP单独管理,也可以不使用Protocol作为key字段来统一管理。\r\n\r\n### 8.1.2. 地址受限型NAT\r\n\r\n- Up Key,内部四元组(CIP、Cport、Protocol)\r\n- Down Key,外部四元组(TIP、Tport、DIP、Protocol)\r\n- Session,Up key + Down key + Start Time + ExpireTime\r\n\r\n上行,内网主机访问外网只要CIP、Cport不变,一定会映射到TIP、Tport;下行,外网主机以DIP为源地址,端口任意访问TIP、Tport可以NAT回内网的CIp和Cport,换个DIP则不行\r\n\r\n\r\n### 8.1.3. 端口受限型NAT\r\n\r\n- Up Key,内部四元组(CIP、Cport、Protocol)\r\n- Down Key,外部四元组(TIP、Tport、DIP、DPORT、Protocol)\r\n- Session,Up key + Down key + Start Time + ExpireTime\r\n\r\n上行,内网主机访问外网只要CIP、Cport不变,一定会映射到TIP、Tport;下行,外网主机以DIP为源地址、DPort访问TIP、Tport可以NAT回内网的CIp和Cport,换个DIP或DPORT都不行。\r\n\r\n### 8.1.4. 对称型NAT\r\n\r\n- Up Key,内部四元组(CIP、Cport、DIP、Dport、Protocol)\r\n- Down Key,外部四元组(TIP、Tport、DIP、Dport、Protocol)\r\n- Session,Up key + Down key + Start Time + ExpireTime\r\n\r\n上行,内网主机访问外网主机,只有(CIP、Cport、DIP、Dport)任意一个发生变化都会产生新会话,即分配新的TIP或TPORT;下行,外网主机只能以固定的DIP和Dport访问固定的TIP、Tport。\r\n\r\n\r\n## 8.2. 会话分配\r\n\r\n这里假设TIP只有一个,实际可以配置多个,通过hash的方式随机选择一个TIP就行了,因此以下我们只讨论在一个TIP的情况下保证会话分配不冲突。\r\n\r\n### 8.2.1. 锥型NAT\r\n\r\n仅需要管理TIP对应的PORT即可,TIP + PORT会固定映射为某个CIP + CPORT的逻辑。因此数据结构可以设置为:\r\n\r\n```c\r\nstruct nat_port_key {\r\n\tuint32_t tip;\r\n\tuint32_t protocol;\r\n}\r\nstruct hash_table *nat_port_table; // nat_port_key 映射到 nat_port_warehouse\r\nstruct nat_port_warehouse {\r\n\t uint32_t allocated_port_bmp; // 使用bitmap记录占用状态\r\n}\r\n```\r\n\r\n### 8.2.2. 对称型NAT\r\n\r\n对于内网主机来说,NAT后的地址和端口并不感知,而对于外网主机来说是感知的,因此对称型NAT需要保证(TIP,Tport,DIP,Dport,Protocl)不一致即可,这五元组只有任意一个不一致,就会对应不同的DOWN Key,自然也能找到对应的Session来找到UP Key,保证可以NAT回去。所以我们只需要基于DOWN Key进行管理会话即可,其中TIP我们可以作为固定值,也就是我们要基于(DIP,Dport,Protocol)来分配Tport。可设计如下数据结构:\r\n\r\n```c\r\nstruct nat_port_key {\r\n uint32_t tip;\r\n\tuint32_t dip;\r\n\tuint32_t dport;\r\n\tuint32_t protocol;\r\n}\r\nstruct hash_table *nat_port_table;//port_key 映射到 nat_port_warehouse\r\nstruct nat_port_warehouse {\r\n\t uint32_t port_allocated_state_bmp; // 使用bitmap记录占用状态\r\n}\r\n```\r\n\r\n> 此方案存在一个缺陷,因为Tport的端口被复用了。假设以下场景,DIP1和DIP2都是提供代理的VIP,他们有同一个后端(SIP,SPORT)。由于DIP1和DIP2不同,因此有可能分配到相同Tport,此时对于后端server来说就会很迷惑,因为它看到了两条不一样的(TIP,TPORT,SIP,SPORT)流,后端服务的协议栈处理的数据流必然也是乱套的,流量就会不同。\r\n\r\n# 9. Refrence\r\n1. [连接跟踪(conntrack):原理、应用及 Linux 内核实现](https://arthurchiao.art/blog/conntrack-design-and-implementation-zh/)\r\n2. [一文看懂DPDK](https://cloud.tencent.com/developer/article/1198333)\r\n3. [什么是DPDK?DPDK的原理及学习学习路线总结](https://zhuanlan.zhihu.com/p/397919872)\r\n4. [AWS Hyperplane浅谈](https://zhuanlan.zhihu.com/p/188735635)\r\n5. [NAT - 网络地址转换](http://arthurchiao.art/blog/nat-zh/)\r\n6. [NAT ALG原理与应用](http://www.h3c.com/cn/d_201206/747033_97665_0.htm)\r\n7. [NAT基本原理及穿透详解(打洞)](https://juejin.cn/post/6844904098572009485)\r\n8. [配置网络地址转换:入门指南](https://www.cisco.com/c/zh_cn/support/docs/ip/network-address-translation-nat/13772-12.html)\r\n9. [Configuring Static and Dynamic NAT Simultaneously](https://www.cisco.com/c/en/us/support/docs/ip/network-address-translation-nat/13778-9.html)"},{"id":"iptables","title":"iptables","date":"2021-10-17T00:00:00.000Z","tags":["计算机网络","network"],"readingTime":8,"slug":"iptables","description":"iptables 是 Linux 防火墙工作在用户空间的管理工具,是 netfilter/iptablesIP 数据包过滤系统是一部分,用来设置、维护和检查 Linux 内核的 IP 数据包过滤规则。 1. 四表五链 数据包在经过每个的时候会按照每个链对应的表依次进行查询匹配执行的操作,如PRERO...","relativePath":"Tech/Os/Linux/Network/iptables.md","rawContent":"---\r\ntitle: iptables\r\ndate: 2021-10-17\r\ncategories:\r\n- 计算机网络\r\ntags:\r\n- 计算机网络\r\n- network\r\n---\r\n\r\niptables 是 Linux 防火墙工作在用户空间的管理工具,是 netfilter/iptablesIP 数据包过滤系统是一部分,用来设置、维护和检查 Linux 内核的 IP 数据包过滤规则。\r\n\r\n\r\n\r\n# 1. 四表五链\r\n数据包在经过每个的时候会按照每个链对应的表依次进行查询匹配执行的操作,如PREROUTING链对应的就是(raw->mangle->nat),每个表按照优先级顺序进行连接,每个表中还可能有多个规则,最终形成一条处理链。iptables的表中存储的就是对应的规则和需要执行的操作。\r\n\r\n![iptables diagram](https://raw.githubusercontent.com/shinerio/shinerio.github.io/blog-images/小书匠/1634463829447.png)\r\n\r\n## 1.1. 四表\r\n### 1.1.1. raw\r\n主要用来决定是否对数据包进行状态跟踪,对应的内核模块为iptable_raw。raw表只使用在PREROUTING链和OUTPUT链上,因为优先级最高,从而可以对收到的数据包在系统进行ip_conntrack(连接跟踪)前进行处理。一旦用户使用了raw表,在某个链上,raw表处理完后,将跳过NAT表和ip_conntrack处理,即不再做地址转换和数据包的链接跟踪处理了。RAW表可以应用在那些不需要做nat的情况下,以提高性能。\r\n### 1.1.2. mangle\r\n主要用来修改数据包的服务类型,生存周期,为数据包设置标记,实现流量整形、策略路由等,对应内核模块miptable_mangle\r\n### 1.1.3. nat\r\n主要用来进行网络地址转换,修改数据包的 IP 地址、端口号信息,对应内核模块iptable_nat。\r\n### 1.1.4. filter\r\n用来对数据包进行过滤,具体的规则要求决定如何处理一个数据包,对应内核模块iptable_filter\r\n> [!note]\r\n> 表的处理优先级:raw>mangle>nat>filter\r\n\r\n## 1.2. 五链\r\n### 1.2.1. PREROUTING\r\n在对数据包做路由选择之前,将应用此链中的规则\r\n### 1.2.2. INPUT\r\n当收目的地址为本机的数据包时,将应用此链中的规则;\r\n### 1.2.3. FORWARD\r\n当收到目的地址非本机的数据包时,将应用此链中的规则。Forward转发需要开启Linux内核中的ip_forward功能。\r\n### 1.2.4. OUTPUT\r\n当本机向外发送数据包时,将应用此链中的规则\r\n### 1.2.5. POSTROUTING\r\n在对数据包做路由选择之后,将应用此链中的规则\r\n\r\n> [!note]\r\n> cat /proc/sys/net/ipv4/ip_forward查看是否开启ip_forward功能。0表示没有启动,1表示已经启动\r\n# 2. iptables使用\r\n## 2.1. 命令\r\niptables的基本语法如下:\r\n```shell\r\niptables [-t 表名] 管理选项 [链名] [匹配条件] [-j 控制类型]\r\n```\r\n### 2.1.1. 表名\r\nraw/mangle/nat/filter,默认为filter\r\n### 2.1.2. 管理选项\r\n - -A:在指定链的末尾添加一条新的规则\r\n - -D:删除指定链中的某一条规则,可删除指定序号或具体内容\r\n - -I:在指定链中插入一条新规则,未指定序号时默认作为第一条规则\r\n - -R:修改、替换指定链中的某一条规则,可指定规则序号或具体内容\r\n - -L:列出指定链中所有的规则,未指定链名,则列出表中的所有链\r\n - -F:清空指定链中所有的规则,未指定链名,则清空表中的所有链\r\n - -P:设置指定链的默认策略\r\n - -n:使用数字形式显示输出结果\r\n - -v:查看规则列表时显示详细的信息\r\n - -h:查看命令帮助信息\r\n - --line-numbers:查看规则列表时,同时显示规则在链中的顺序号\r\n### 2.1.3. 链名\r\n RREROUTING/INPUT/FORWARD/OUTPUT/POSTROUTING\r\n### 2.1.4. 匹配条件\r\n - -s 指定源地址或网段,逗号隔开\r\n - -d 指定目的地址或网段,逗号隔开\r\n - ! 取反,如! -s 192.168.0.200,表示源地址不是192.168.0.200的都匹配。取反操作是-s和-d选项不能同时指定多个\r\n - -p协议。tcp, udp, udplite, icmp, icmpv6,esp, ah, sctp, mh或all\r\n - -i,指定流入数据包网卡,-i eth0,匹配从eth0流入的数据包,只能用于PREROUTING/INPUT/FORWARD\r\n - -o,指定流出数据包网卡,-i eth0,匹配从eth0流出的数据包,只能用于FORWARD/OUTPUT/POSTROUTING\r\n - --sport,指定源端口,支持范围。如--sport 10000:20000\r\n - --dport,指定目的端口,支持范围。如--port 10000:20000\r\n - -m,选项用于指定要使用的匹配模块(match module),基于不同的标准(如数据包的状态、协议类型、端口范围等)来筛选数据包\r\n#### 2.1.4.1. -m模块示例\r\n##### 2.1.4.1.1. multiport模块\r\n支持指定多个端口。如`-m multiport --sports 22,10000:20000`\r\n##### 2.1.4.1.2. state模块\r\n可依据数据包的连接状态\r\n来匹配,常见的连接状态有 `NEW`(新连接)、`ESTABLISHED`(已建立的连接)、`RELATED`(相关连接)和 `INVALID`(无效连接)。\r\n```shell\r\niptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\r\n```\r\n只允许已建立和相关的连接进入\r\n##### 2.1.4.1.3. iprange\r\n模块可用于匹配指定范围内的 IP 地址。\r\n```shell\r\niptables -A INPUT -m iprange --src-range 192.168.1.100-192.168.1.200 -j ACCEPT\r\n```\r\n允许来自特定 IP 范围(如 192.168.1.100 - 192.168.1.200)的数据包进入\r\n##### 2.1.4.1.4. limit匹配模块\r\n```shell\r\niptables -A INPUT -p tcp --dport 22 -m state --state NEW -m limit --limit 10/min -j ACCEPT\r\n```\r\n限制每分钟最多 10 个新的 SSH 连接请求\r\n### 2.1.5. 控制类型\r\n - 接收ACCEPT\r\n - 拒绝REJECT\r\n - 丢弃DROP\r\n - 日志LOG\r\n> [!note]\r\n> REJECT和DROP的区别。\r\n> REJECT动作会返回一个拒绝(终止)数据包(TCP FIN或UDP-ICMP-PORT-UNREACHABLE),明确的拒绝对方的连接动作。连接马上断开,Client会认为访问的主机不存在。\r\n> DROP动作只是简单的直接丢弃数据,并不反馈任何回应。需要Client等待超时,Client容易发现自己被防火墙所阻挡。\r\n## 2.2. 示例\r\n### 2.2.1. 丢且来自x.x.x.x的数据包\r\n```shell\r\nBLOCK_THIS_IP=\"x.x.x.x\"\r\niptables -A INPUT -s \"$BLOCK_THIS_IP\" -j DROP\r\n```\r\n### 2.2.2. 阻止来自IP地址x.x.x.x 且入接口为eth0,协议为tcp的包\r\n```shell\r\nBLOCK_THIS_IP=\"x.x.x.x\"\r\niptables -A INPUT -i eth0 -p tcp -s \"$BLOCK_THIS_IP\" -j DROP\r\n```\r\n### 2.2.3. 丢弃已建链的数据包\r\n```shell\r\niptables -A OUTPUT -d 192.168.1.100 -p tcp --dport 5432 -m state --state ESTABLISHED -j DROP\r\niptables -A INPUT -s 192.168.1.100 -p tcp --sport 5432 -m state --state ESTABLISHED -j DROP\r\n```\r\n### 2.2.4. 允许来自eth0的所有SSH的连接请求,目标端口为22的数据包\r\n```shell\r\niptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT\r\niptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT\r\n```\r\n### 2.2.5. 不允许外部主机ping内部主机\r\n```shell\r\niptables -A INPUT -p icmp --icmp-type echo-request -j DROP\r\niptables -A OUTPUT -p icmp --icmp-type echo-reply -j DROP\r\n```\r\n### 2.2.6. 允许内部主机ping外部主机\r\n```shell\r\niptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT\r\niptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT\r\n```\r\n### 2.2.7. 日志\r\n对于INPUT链中的所有操作都记录到日志中,添加日志前缀“*** INPUT *** ”并设定日志级别为debug\r\n```shell\r\niptables -A INPUT -j LOG --log-prefix \"*** INPUT ***\" --log-level debug\r\n```\r\n### 2.2.8. 自定义链\r\n通过-N参数创建自定义链,将BLOCK链作为控制目标,如:\r\n```shell\r\niptables -N BLOCK\r\niptables -A BLOCK -p tcp -s 10.1.1.92/32 --dport 80 -j DROP\r\niptables -I INPUT 1 -p tcp --dport 80 -j BLOCK\r\n```\r\nINPUT链中匹配规则1的包都会转入BLOCK链中,若到达了BLOCK链的结尾(即未被链中的规则匹配),则会回到INPUT链的下一条规则。如果在子链中匹配了,则就相当于在父链中匹配了,那么它不会再经过父链中的其他规则。\r\n> [!note]\r\n> iptables -X BLOCK,删除自定义链,iptable -E BLOCK BLOCK_NEW重命名"},{"id":"tcpdump","title":"tcpdump","date":"2020-08-09T00:00:00.000Z","tags":["计算机网络","network"],"readingTime":6,"slug":"tcpdump","description":"tcpdump是一个优秀的网络分析工具,提供了强大且简单的接口。 1. options - -i any 监听所有的网卡接口,用来查看是否有网络流量 - -i eth0 只监听eth0网卡接口 - -D 显示可用的接口列表 - -n 不要解析主机名 - -nn 不要解析主机名或者端口名 - -q 显...","relativePath":"Tech/Os/Linux/常用运维命令/tcpdump.md","rawContent":"---\r\ntitle: tcpdump\r\ndate: 2020-08-09\r\ncategories:\r\n- 计算机网络\r\ntags:\r\n- 计算机网络\r\n- network\r\n---\r\n\r\ntcpdump是一个优秀的网络分析工具,提供了强大且简单的接口。\r\n\r\n\r\n\r\n# 1. options\r\n\r\n- **-i any** 监听所有的网卡接口,用来查看是否有网络流量\r\n- **-i eth0** 只监听eth0网卡接口\r\n- **-D** 显示可用的接口列表\r\n- **-n** 不要解析主机名\r\n- **-nn** 不要解析主机名或者端口名\r\n- **-q** 显示更少的输出(更加quiet)\r\n- **-t** 输出可读的时间戳\r\n- **-tttt** 输出最大程度可读的时间戳\r\n- **-X** 以hex和ASCII两种形式显示包的内容\r\n- **-XX** 与-X类似,增加以太网header的显示\r\n- **-v, -vv, -vvv** 显示更加多的包信息\r\n- **-c** 只读取x个包,然后停止\r\n- **-s** 指定每一个包捕获的长度,单位是byte,使用`-s0`可以捕获整个包的内容\r\n- **-S** 输出绝对的序列号\r\n- **-e** 获取以太网header\r\n- **-E** 使用提供的秘钥解密IPSEC流量\r\n\r\n# 2. Example\r\n\r\n## 2.1. 监听所有网卡接口\r\n\r\n```shell\r\ntcpdump -i any\r\n```\r\n\r\n## 2.2. 监听指定网卡接口\r\n\r\n```bash\r\ntcpdump -i eth0\r\n```\r\n\r\n## 2.3. 显示可用接口列表\r\n\r\n```bash\r\ntcpdump -D\r\n```\r\n\r\n## 2.4. 指定ip\r\n\r\n```bash\r\ntcpdump host 8.8.8.8\r\n```\r\n\r\n08:12:42.050004 IP 64.64.238.73.16clouds.com > dns.google: ICMP echo request, id 27320, seq 1, length 64\r\n08:12:42.059723 IP dns.google > 64.64.238.73.16clouds.com: ICMP echo reply, id 27320, seq 1, length 64\r\n08:12:43.050762 IP 64.64.238.73.16clouds.com > dns.google: ICMP echo request, id 27320, seq 2, length 64\r\n08:12:43.051410 IP dns.google > 64.64.238.73.16clouds.com: ICMP echo reply, id 27320, seq 2, length 64\r\n08:12:44.058965 IP 64.64.238.73.16clouds.com > dns.google: ICMP echo request, id 27320, seq 3, length 64\r\n08:12:44.059281 IP dns.google > 64.64.238.73.16clouds.com: ICMP echo reply, id 27320, seq 3, length 64\r\n\r\n## 2.5. 指定源或目的地址\r\n\r\n```bash\r\ntcpdump src 64.64.238.73\r\ntcpdump dst 8.8.8.8\r\n```\r\n\r\n## 2.6. 指定子网\r\n\r\n```bash\r\ntcpdump net 192.168.0.0/16\r\n```\r\n\r\n## 2.7. 显示绝对序列号\r\n\r\n```bash\r\ntcpdump -S host google.com\r\n```\r\n\r\n08:39:14.977056 IP 64.64.238.73.16clouds.com.56270 > lax31s12-in-f14.1e100.net.https: Flags [P.], seq 1389390438:1389390527, ack 2289481827, win 292, options [nop,nop,TS val 2079502004 ecr 4062705777], length 89\r\n08:39:14.977429 IP lax31s12-in-f14.1e100.net.https > 64.64.238.73.16clouds.com.56270: Flags [P.], seq 2289481827:2289481858, ack 1389390527, win 240, options [nop,nop,TS val 4062731918 ecr 2079502004], length 31\r\n08:39:14.977457 IP 64.64.238.73.16clouds.com.56270 > lax31s12-in-f14.1e100.net.https: Flags [P.], seq 1389390527:1389390822, ack 2289481858, win 292, options [nop,nop,TS val 2079502005 ecr 4062731918], length 295\r\n08:39:14.982669 IP lax31s12-in-f14.1e100.net.https > 64.64.238.73.16clouds.com.56270: Flags [.], ack 1389390822, win 244, options [nop,nop,TS val 4062731923 ecr 2079502005], length 0\r\n08:39:15.016514 IP lax31s12-in-f14.1e100.net.https > 64.64.238.73.16clouds.com.56270: Flags [P.], seq 2289481858:2289482742, ack 1389390822, win 244, options [nop,nop,TS val 4062731957 ecr 2079502005], length 884\r\n08:39:15.016558 IP lax31s12-in-f14.1e100.net.https > 64.64.238.73.16clouds.com.56270: Flags [P.], seq 2289482742:2289484160, ack 1389390822, win 244, options [nop,nop,TS val 4062731957 ecr 2079502005], length 1418\r\n08:39:15.016578 IP 64.64.238.73.16clouds.com.56270 > lax31s12-in-f14.1e100.net.https: Flags [.], ack 2289484160, win 337, options [nop,nop,TS val 2079502044 ecr 4062731957], length 0\r\n08:39:15.016593 IP lax31s12-in-f14.1e100.net.https > 64.64.238.73.16clouds.com.56270: Flags [P.], seq 2289484160:2289484819, ack 1389390822, win 244, options [nop,nop,TS val 4062731957 ecr 2079502005], length 659\r\n08:39:15.016603 IP lax31s12-in-f14.1e100.net.https > 64.64.238.73.16clouds.com.56270: Flags [P.], seq 2289484819:2289486237, ack 1389390822, win 244, options [nop,nop,TS val 4062731957 ecr 2079502005], length 1418\r\n08:39:15.016608 IP 64.64.238.73.16clouds.com.56270 > lax31s12-in-f14.1e100.net.https: Flags [.], ack 2289486237, win 382, options [nop,nop,TS val 2079502044 ecr 4062731957], length 0\r\n\r\n> [!note]\r\n> - seq为tcp报文的序列号,按包内数据字节长度加上,如包内数据是21字节,而当前IP1发到IP2的包的seq是10的话,那下个IP1发到IP2的包的seq就是10+21=31\r\n> - ack为已经收到的报文的序列号,告诉对方下次从seq=ack处继续发报文\r\n> - win是剩余窗口大小,如win=0,表示窗口已满,告知对方暂停发包\r\n## 2.8. 不解析ip和端口号\r\n\r\n```bash\r\ntcpdump -nn host 8.8.8.8\r\n```\r\n\r\n08:46:48.586465 IP 64.64.238.73 > 8.8.8.8: ICMP echo request, id 27537, seq 1, length 64\r\n08:46:48.586921 IP 8.8.8.8 > 64.64.238.73: ICMP echo reply, id 27537, seq 1, length 64\r\n08:46:49.594835 IP 64.64.238.73 > 8.8.8.8: ICMP echo request, id 27537, seq 2, length 64\r\n08:46:49.596609 IP 8.8.8.8 > 64.64.238.73: ICMP echo reply, id 27537, seq 2, length 64\r\n08:46:50.594823 IP 64.64.238.73 > 8.8.8.8: ICMP echo request, id 27537, seq 3, length 64\r\n08:46:50.595311 IP 8.8.8.8 > 64.64.238.73: ICMP echo reply, id 27537, seq 3, length 64\r\n\r\n## 2.9. 指定协议\r\n\r\n```bash\r\ntcpdump icmp\r\n```\r\n\r\n## 2.10. 指定端口范围\r\n\r\n```bash\r\ntcpdump portrange 0-5000\r\n```\r\n\r\n## 2.11. 基于包的大小过滤流量\r\n\r\n```bash\r\ntcpdump less 32\r\ntcpdump greater 64\r\ntcpdump <=128\r\n```\r\n\r\n## 2.12. 读取指定数量的包\r\n\r\n```bash\r\ntcpdump -c 10\r\n```\r\n\r\n## 2.13. 导入/导出\r\n\r\n可以使用-w导出成PCAP,使用wireshark等可视化工具打开,或使用-r导入\r\n```bash\r\ntcpdump -c 10 -w test.pcap\r\ntcpdump -r test.pcap\r\n```\r\n\r\n# 3. 组合\r\n- AND: **and** or **&&**\r\n- OR: **or** or **||**\r\n- EXCEPT: **not** or !\r\n\r\n```bash\r\ntcpdump -nnvvS src 10.5.2.3 and dst port 3389\r\ntcpdump -nvX src net 192.168.0.0/16 and dst net 10.0.0.0/8 or 172.16.0.0/16\r\ntcpdump dst 192.168.0.2 and src net and not icmp\r\ntcpdump 'src 10.0.2.3 and (dst port 3389 or 22)'\r\n```\r\n\r\n# 4. 指定的TCP标识\r\n`tcp[index]`,其中index表示数据包中的第index个字节(index从零开始),第13个字节主要用于存储tcp标识符。`tcp[13]=x`标识第13个字节值为x,`tcp[13] & x`得到与x与之后的结果,可以用于提取某一bit位的值。\r\n\r\n![[TCP与UDP协议#1.1. 首部格式]]\r\n\r\n## 4.1. 显示所有的URGENT (URG)包\r\n32二进制位`00100000`,用于取第13个字节的第3位,即URG标识。\r\n```\r\ntcpdump 'tcp[13] & 32!=0'\r\n```\r\n## 4.2. 显示所有的ACKNOWLEDGE (ACK)包\r\n```\r\ntcpdump 'tcp[13] & 16!=0'\r\n```\r\n## 4.3. 显示所有的PUSH(PSH)包\r\n```\r\ntcpdump 'tcp[13] & 8!=0'\r\n```\r\n## 4.4. 显示所有的RESET(RST)包\r\n```\r\ntcpdump 'tcp[13] & 4!=0'\r\n```\r\n## 4.5. 显示所有的SYNCHRONIZE (SYN) 包\r\n```\r\ntcpdump 'tcp[13] & 2!=0'\r\n```\r\n## 4.6. 显示所有的FINISH(FIN)包\r\n```\r\ntcpdump 'tcp[13] & 1!=0'\r\n```\r\n## 4.7. 显示所有的SYNCHRONIZE/ACKNOWLEDGE (SYNACK)包\r\n```\r\ntcpdump 'tcp[13]=18'\r\n```\r\n## 4.8. 参考链接\r\n[tcpdump简明教程](https://github.com/mylxsw/growing-up/blob/master/doc/tcpdump简明教程.md)"},{"id":"数据中心网络架构","title":"数据中心网络架构","date":"2020-05-02T00:00:00.000Z","tags":["network"],"readingTime":8,"slug":"数据中心网络架构","description":"0.1. 数据中心 根据维基百科释义,指用于安置计算机系统及相关部件的设施,例如电信和存储系统。数据中心是全球协作的特定设备网络,用来在internet网络基础设施上传递、加速、展示、计算和存储数据信息。 0.2. 传统数据中心网络架构 如图1所示,传统的大型数据中心网络通常采用三层架构。cisco...","relativePath":"Tech/Network/DCN/数据中心网络架构.md","rawContent":"---\r\ntitle: 数据中心网络架构\r\ndate: 2020-05-02\r\ncategories:\r\n- network\r\ntags:\r\n- network\r\n\r\n---\r\n\r\n## 0.1. 数据中心\r\n\r\n根据维基百科释义,[数据中心](https://zh.wikipedia.org/wiki/数据中心)指用于安置计算机系统及相关部件的设施,例如电信和存储系统。数据中心是全球协作的特定设备网络,用来在internet网络基础设施上传递、加速、展示、计算和存储数据信息。\r\n\r\n\r\n\r\n## 0.2. 传统数据中心网络架构\r\n\r\n如图1所示,传统的大型数据中心网络通常采用三层架构。cisco称之为分级的互联网网络模型(hierarchical inter-networking model)。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200430163541.png)\r\n\r\n
\r\n图 1:传统数据中心网络三层架构\r\n
\r\n\r\n- 接入层:接入交换机通常位于机架顶部,所以它们也被称为ToR(Top of Rack)交换机,它们物理连接各种服务器。\r\n- 汇聚层:汇聚层交换机连接接入层交换机,同时提供其他服务,如防火墙、SSL offload、入侵检测、网络分析等。\r\n- 核心层:核心层交换机为进出数据中心的包提供高速的转发,为多个汇聚层提供连接性,核心交换机通常为整个网络提供一个弹性的L3路由网络\r\n\r\n通常情况下,汇聚层是L2和L3网络的分界点,汇聚交换机以下的是L2网络,以上的是L3网络。每组汇聚层交换机管理一个PoD,每一个PoD内都是独立的VLAN网络,对应一个L2广播域,因此服务器在PoD内迁移不必修改IP地址和默认网关。汇聚层是一个HA(High Available)模式,汇聚层交换机和接入层交换机之间通常使用STP(Spanning Tree Protocol,生成树协议)。STP使得对于一个VLAN网络只有一个汇聚层交换机可用(防止出现链路环导致广播风暴),其他汇聚层交换机作为容灾备份节点。\r\n\r\n > [!PoD] \r\n > PoD(Point of Deliery)是一个包括网络、计算、存储和应用等组成部分共同工作来提供网络服务的模块。PoD是一个模块化设计的概念,最大限度地提高了数据中心的模块化、可拓展性和可管理性。\r\n\r\n> [!STP]\r\n> 由于STP收敛性能的问题,一般情况下STP网络的规模不会超过100台交换机。STP的机制导致了二层链路利用率不足(同时只有一个汇聚层交换机被激活)。\r\n\r\n## 0.3. 云计算时代的数据中心网络\r\n\r\n### 0.3.1. 云计算时代的数据中心网络的典型特征\r\n\r\n- 服务器利用率高:云计算时代的服务器不局限于物理迁移,虚拟化技术使得一台物理服务器上可以同时运行多个虚拟服务器。服务器利用率的提高,导致网络的整体流量增大,才而导致要求更小的超占比。\r\n- 东西向流量增大:现代软件系统大都采用分层结构,一个系统的多个组件通常分布在多个虚拟机/容器中。例如,常见的web服务都会包括前后端分离模式,前端、后端、数据库分别由不同服务器提供。其次,现代云计算厂商也会把服务器分为计算密集型和I/O密集型,在计算节点和存储节点之间产生了大量的流量。\r\n- 动态迁移性强:云计算时代资源被池化,虚拟机的创建、销毁和迁移变得很频繁。\r\n\r\n!!! note 数据中心的流量\r\n - 南北向流量:数据中心之外的客户端(互联网)和数据中心服务器之间的流量\r\n - 东西向流量:数据中心内的服务器之间的流量。\r\n - 跨数据中心流量:跨数据中心的流量,例如数据中心之间的灾备,私有云和公有云之间的通讯。\r\n\r\n!!! note 超占比\r\n 超占比(oversubscription)是指网络的上游带宽与下游容量之比。比如图1中,一个汇聚层交换机接了四个接入层交换机,假如接入层交换机带宽都是1Gbps,按照理论来说汇聚层与接入层之间的带宽至少应该是4Gbps。但是通常情况下接入层的交换机很少能同时跑满所有带宽,所以我们可以设计汇聚层交换机只用提供2Gbps,则超占比为$(4\\times1):2 = 2:1$\r\n\r\n### 0.3.2. 大二层网络架构\r\n\r\n为了增强网络的动态迁移能力,适应云计算时代资源池化的特点,我们需要一个大二层的网络架构,使得计算资源可以任意分配。大二层架构下的整个数据中心网络都是一个L2广播域,这样服务器就可以在数据中心内任意创建、迁移,而不需要对IP地址或默认网关做修改。大二层网络架构下L2和L3的分界在核心交换机,核心交换机以下也就是整个数据中心,是L2网络(可以包含多个VLAN,VLAN之间通过三层核心交换机做路由)。大二层网络架构虽然使得虚机网络能够灵活创建,但是带来的问题也是明显的。共享的L2广播域带来的BUM(Broadcast·,Unknown Unicast,Multicast)风暴随着网络规模的增加而明显增加,最终将影响正常的网络流量。\r\n\r\n!!! note \"\"\r\n 大二层网络架构和传统三层架构都有多个VLAN。传统三层架构以汇聚层交换机构成VLAN,不同汇聚层交换机之间VLAN是隔离的(也就是服务器如果迁移到另一个汇聚层,则VLAN一定变化,导致IP和默认网关需要更改)。大二层架构下,VLAN的划分是在核心层交换机下的,服务器迁移到另一个汇聚层,仍可以处于原来的VLAN环境。\r\n\r\n\r\n\r\n## 0.4. 引用\r\n\r\n1. [What is “oversubscription” in Networking?](https://networkengineering.stackexchange.com/questions/60002/what-is-oversubscription-in-networking)\r\n2. [云计算时代,数据中心架构三层到大二层的演变](https://juejin.im/post/5c723943f265da2d943f69c1)\r\n3. [数据中心网络架构浅谈(一)](https://zhuanlan.zhihu.com/p/29881248)\r\n\r\n"},{"id":"应用层DNS协议解析","title":"应用层DNS协议","date":"2020-04-29T00:00:00.000Z","tags":["network"],"readingTime":15,"slug":"应用层dns协议解析","description":"(Domain Name System)域名解析服务采用架构,是一个应用层协议。的作用是将人类可读的域名(如:www.shinerio.cc) 转换为机器可读的 IP 地址(如:1111.111.111.111)。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。协议建立...","relativePath":"Tech/Network/应用层/DNS/应用层DNS协议解析.md","rawContent":"---\r\ntitle: 应用层DNS协议\r\ndate: 2020-04-29\r\ncategories:\r\n- network\r\ntags:\r\n- network\r\n---\r\n`DNS`(Domain Name System)域名解析服务采用`C/S`架构,是一个应用层协议。`DNS`的作用是将人类可读的域名(如:www.shinerio.cc) 转换为机器可读的 IP 地址(如:1111.111.111.111)。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。`DNS`协议建立在`UDP`或`TCP`协议之上,默认使用53号端口。客户端默认通过UDP协议进行通信,但是由于广域网中不适合传输过大的`UDP`数据包,因此规定当报文长度超过了512字节时,应转换为使用`TCP`协议进行数据传输。此时可能会出现如下的两种情况:\r\n- 客户端认为`UDP`响应包长度可能超过512字节,主动使用`TCP`协议\r\n- 客户端使用UDP协议发送DNS请求,服务端发现响应报文超过了 512 字节,在截断的`UDP`响应报文中将`TC`设置为1,以通知客户端该报文已经被截断,客户端收到之后再发起一次`TCP`请求(该特性通常也被安全厂商作为一种有效防御`DNS Query Flood`攻击的手段)\r\n\r\n\r\n \r\n# 1. 基本概念\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424141937.png)\r\n\r\n## 1.1. 域名结构\r\n常见的域名格式有:\r\n- www.google.com\r\n- mail.google.com \r\n- scholar.google.com \r\n\r\n【顶级域名】: .com\r\n【二级域名】: google\r\n【三级域名】:www/mail/scholar\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424142522.png)\r\n\r\n## 1.2. DNS记录类型\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424152649.png)\r\n\r\n### 1.2.1. A记录\r\nA记录是用来指定主机名(或域名)对应的IP地址记录。如设置二级域名`shinerio.cc`对应`ip`地址为`185.199.111.153`(gitpage服务器地址)等。当然,也可以为三级域名`mail.shinerio.cc`绑定对应`ip`地址。一个域名可以对应多条`A`记录,即对应多个ip地址,实现`DNS`负载均衡。\r\n\r\n主机记录`www`解析后域名为`www.shinerio.cc`。`@`直接解析主域名`shinerio.cc`,相当于`www.shinerio.cc`。`*`匹配所有域名`*.shinerio.cc`,将所有`shinerio.cc`下的子域名指向同一服务器地址。`mail`将域名解析为`mail.shinerio.cc`,通常用于解析邮箱服务器。主机记录`xxx`解析后域名为`xxx.shinerio.cc`,用于解析`xxx`类型服务。\r\n\r\n### 1.2.2. AAAA记录\r\nAAAA记录和A记录的区别在于,AAAA记录将域名解析到一个`ipv6`地址。\r\n\r\n### 1.2.3. NS记录\r\n`NS记录`指定解析域名或子域名的DNS服务器。\r\n\r\n### 1.2.4. MX记录\r\nMX记录(邮件交换)用来指定接收信息的邮件服务器。建立邮箱时,一般会根据邮箱服务商提供的MX记录填写此记录。\r\n\r\n### 1.2.5. CNAME\r\n`CNAME`记录将多个名字映射到另外一个域名。如将shinerio.cc记录别名为`shinerio.github.io`,这样用`shinerio.cc`替代`shinerio.github.io`访问gitpage提供的个人博客主页。\r\n\r\n使用`CNAME`记录可以很方便地变更IP地址。如果一台服务器有100个网站,每个网站有一个域名。如果使用A记录,那么服务器ip更改时,则需要修改100条A记录。如果使用CNAME指向同一域名,那么该台服务器变更IP时,只需要变更别名的A记录就可以。\r\n\r\n# 2. 域名解析\r\n## 2.1. 域名服务器层次\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424144017.png)\r\n\r\n### 2.1.1. 根域名服务器\r\n最高层次的域名服务器,也是最重要的域名服务器。全球有13个根域名服务器名称,并不是一个名字对应一台物理服务器的地址,一个根服务器的名字可以作为入口对应一组服务器集群来提供域名解析服务。\r\n- a.root-servers.net.\r\n- b.root-servers.net.\r\n- c.root-servers.net.\r\n- d.root-servers.net.\r\n- e.root-servers.net.\r\n- f.root-servers.net.\r\n- g.root-servers.net.\r\n- h.root-servers.net.\r\n- i.root-servers.net.\r\n- j.root-servers.net.\r\n- k.root-servers.net.\r\n- l.root-servers.net.\r\n- m.root-servers.net.\r\n\r\n### 2.1.2. 顶级域名服务器\r\n主要负责管理在该顶级域名服务器注册的下一级域名(二级域名)。所有顶级域名服务器的名称和IP地址是在根服务器注册的,也就是说,根域名服务器知道所有的顶级域名服务器的名称和IP地址。\r\n\r\n### 2.1.3. 权威域名服务器\r\n负责一个区的域名服务器,顶级域名服务器也可以算作是权威域名服务器,只不过由于其特殊性,我们专门把它划分为一类。因此权威域名服务器通常是指顶级域名以下的管理二级、三级、四级等域名的服务器。\r\n\r\n### 2.1.4. 本地域名服务器\r\n当一个主机(个人电脑)发出DNS请求时,查询请求就被发送到本地域名服务器,本地域名服务器负责回答这个查询,或者代替主机向域名空间中不同层次的权威域名服务器查询,再把查询的结果返回给主机。\r\n\r\n## 2.2. 解析过程\r\nDNS解析过程其实可以看做一个多岔树递归查询算法,从根节点出发,递归找下属域名服务器,直到解析成功。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424150647.png)\r\n\r\n在浏览器中输入`www.shinerio.cc`域名,操作系统会先检查自己本地的`hosts`文件是否存在域名映射关系。如果存在映射关系,则根据映射完成域名解析;如果没有映射,则查找本地DNS缓存,是否存在域名映射关系,如果有,则直接返回,完成域名解析。\r\n\r\n如果`hosts`与本地DNS缓存都没有相应的域名映射关系,首先会找网络配置参数中设置的首选DNS服务器(记为A-DNS服务器),此服务器收到查询时,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。如果要查询的域名,不由A-DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性。\r\n\r\n如果A-DNS服务器本地区域文件与缓存解析都失效,则根据A-DNS服务器的设置(是否设置转发器)进行查询。\r\n\r\n- 如果未用转发模式,本地DNS就把请求发至根DNS服务器(记为B-DNS服务器),B-DNS服务器收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。A-DNS服务器收到IP信息后,将会联系负责.com域的这台服务器(记为C-DNS服务器)。C-DNS服务器收到请求后,如果自己无法解析,它就会找一个管理.com域的下一级DNS服务器地址(记为D-DNS服务器)给A-DNS服务器。当A-DNS服务器收到这个地址后,就会找D-DNS服务器,重复上面的动作,进行查询,直至找到`www.shinerio.cc`主机。\r\n- 如果用的是转发模式,A-DNS服务器就会把请求转发至上一级DNS服务器(记为E-DNS服务器),由E-DNS服务器进行解析;如果E-DNS服务器如果不能解析,那么会继续找根DNS或把转请求转至上上级,以此循环。\r\n\r\n# 3. 协议报文格式\r\n`DNS`采用`UDP`或`TCP`传输,主要是查看`DNS`报文首部中的标志字段。当客户端发出`DNS`查询请求,从服务器收到的响应报文中的`TC`(截断标志)比特被置为1时,表示应答总长度超过512字节,只返回前512个字节,这时DNS就需要使用TCP重发原来的查询请求。因为在`UDP`的应用程序中,其应用程序被限制在512个字节或更小,因此`DNS`报文穿数据流只能有512字节,而`TCP`能将用户的数据流分为一些报文段,因此`TCP`就能用多个报文段去传超过512字节的数据流或是任意长度的数据流。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424155506.png)\r\n## 3.1. 头部\r\n### 3.1.1. 会话标识(字节)\r\n会话标识是DNS报文的ID标识,对于请求报文和其对应的应答报文,这个字段是相同的,通过它可以区分DNS应答报文是哪个请求的响应。\r\n### 3.1.2. 标志(2字节)\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424155715.png)\r\n\r\n| 字段 | 描述 |\r\n| ------------ | ---------------------------------------------- |\r\n| QR(bit) | 查询/响应标志,0为查询,1为响应 |\r\n| opcode(4bit) | 0表示标准查询,1表示反向查询,2表示服务器状态请求 |\r\n| AA(1bit) | 表示授权回答 |\r\n| TC(1bit) | 表示可截断的 |\r\n| RD(1bit) | 表示期望递归 |\r\n| RA(1bit) | 表示可用递归 |\r\n| rcode(4bit) | 表示返回码,0表示没有差错,3表示名字差错,2表示服务器错误(Server Failure) |\r\n\r\n### 3.1.3. Questions(2字节)\r\n问题计数,DNS查询请求的数目\r\n\r\n### 3.1.4. Answers(2字节)\r\n回答资源计数,DNS响应的数目\r\n\r\n### 3.1.5. Authority RRs(2字节)\r\n权威名称服务器计数,权威名称服务器的数目。\r\n\r\n### 3.1.6. Additional RRs(2字节)\r\n额外的记录数目(权威名称服务器对应`IP`地址的数目)\r\n\r\n## 3.2. 正文 \r\n### 3.2.1. Queries\r\n`Queries`部分是用来显示DNS下查询请求的问题,通常只有一个问题。该部分包含正在进行的查询信息,包含查询名(被查询主机名字)、查询类型、查询类。\r\n#### 3.2.1.1. 查询名\r\n一般为要查询的域名,有时也会是 IP 地址,用于反向查询,一般的格式如下图所示。\r\n
\r\n
\r\nFig 1:NAME格式\r\n
\r\n#### 查询类类型\r\nDNS 查询请求的资源类型。通常查询类型为 A 类型,表示由域名获取对应的 IP 地址。\r\n\r\n
\r\nTab 1:查询类型\r\n
\r\n\r\n| 类型 | 助记符 | 说明 |\r\n| ------ | ------ | ------ |\r\n| 1 | A | 由域名获得IPv4地址 |\r\n| 2 | NS | 查询域名服务器 |\r\n| 5 | CNAME | 查询规范名称 |\r\n| 6 | SOA | 开始授权 |\r\n| 11 | WKS | 熟知服务 |\r\n| 12 | PTR | 把IP地址转换成域名 |\r\n| 13 | HINFO | 主机信息 |\r\n| 15 | MX | 邮件交换 |\r\n| 28 | AAAA | 由域名获得IPv6地址 |\r\n| 252 | AXFR | 传送整个区的请求 |\r\n| 255 | ANY | 对所有记录的请求 |\r\n\r\n#### 3.2.1.2. 查询类\r\n通常为`0x0001`,表明是互联网地址。\r\n\r\n### 3.2.2. Answers,Authoritative namesversers,Additional recoreds\r\n这三个部分格式一样,如下图所示。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424160818.png)\r\n- Name。它的格式和Queries区域的查询名字字段是一样的。有一点不同就是,当报文中域名重复出现的时候,该字段使用2个字节的偏移指针来表示。比如,在资源记录中,域名通常是查询问题部分的域名的重复,因此用2字节的指针来表示,具体格式是最前面的两个高位是11,用于识别指针。其余的14位从DNS报文的开始处计数(从0开始),指出该报文中的相应字节数。一个典型的例子,C00C(1100000000001100,12正好是头部的长度,其正好指向Queries区域的查询名字字段)。\r\n- 生存时间。以秒为单位,表示的是资源记录的生命周期,一般用于当地址解析程序取出资源记录后决定保存及使用缓存数据的时间,它同时也可以表明该资源记录的稳定程度,极为稳定的信息会被分配一个很大的值(比如86400,这是一天的秒数)。\r\n- data。该字段是一个可变长字段,表示按照查询段的要求返回的相关资源记录的数据。可以是Address(表明查询报文想要的回应是一个IP地址)或者CNAME(表明查询报文想要的回应是一个规范主机名)等。\r\n## 3.3. wireshark抓包分析\r\n### 3.3.1. 请求报文\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424163208.png)\r\n\r\n### 3.3.2. 响应报文\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424163312.png)\r\n\r\n# 4. nslookup和dig\r\n通过命令行查询域名对应ip有两种方式:\r\n- 使用``nslookup shinerio.cc``查询域名ip\r\n- 使用`dig shinerio.cc`查询域名ip\r\n# 5. DNS Over TCP\r\n客户端默认通过UDP协议进行通信,但是由于广域网中不适合传输过大的`UDP`数据包,因此规定当报文长度超过了512字节时,应转换为使用`TCP`协议进行数据传输。此时可能会出现如下的两种情况:\r\n- 客户端认为`UDP`响应包长度可能超过512字节,主动使用`TCP`协议\r\n- 客户端使用UDP协议发送DNS请求,服务端发现响应报文超过了 512 字节,在截断的`UDP`响应报文中将`TC`设置为1,以通知客户端该报文已经被截断,客户端收到之后再发起一次`TCP`请求(该特性通常也被安全厂商作为一种有效防御`DNS Query Flood`攻击的手段)\r\n\r\n使用以下脚本`python3 dns_query.py shinerio.site 8.8.8.8`可以使用TCP发起DNS请求。\r\n```\r\nimport dns.resolver\r\nimport dns.query\r\nimport dns.rdatatype\r\n\r\n\r\ndef tcp_dns_query(domain, dns_server):\r\n resolver = dns.resolver.Resolver()\r\n resolver.nameservers = [dns_server]\r\n query = dns.message.make_query(domain, dns.rdatatype.A)\r\n try:\r\n response = dns.query.tcp(query, dns_server)\r\n for answer in response.answer:\r\n for item in answer.items:\r\n print(item.address)\r\n except dns.resolver.NXDOMAIN:\r\n print(f\"域名 {domain} 未找到。\")\r\n except dns.resolver.NoAnswer:\r\n print(f\"DNS服务器 {dns_server} 没有返回答案。\")\r\n except dns.resolver.Timeout:\r\n print(f\"查询 {domain} 超时。\")\r\n\r\n\r\nif __name__ == \"__main__\":\r\n import sys\r\n\r\n if len(sys.argv)!= 3:\r\n print(\"用法: python tcp_dns_query.py [域名] [DNS服务器地址]\")\r\n else:\r\n domain = sys.argv[1]\r\n dns_server = sys.argv[2]\r\n tcp_dns_query(domain, dns_server)\r\n```\r\n\r\n![[dns_over_tcp.pcapng]]\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241229214126094.png)\r\n"},{"id":"TCP与UDP协议","title":"计算机网络(九)—— 传输层","date":"2020-04-27T00:00:00.000Z","tags":["计算机网络"],"readingTime":11,"slug":"tcp与udp协议","description":"传输层架构在网络层之上,在两台计算机进程之间传输数据,常见的传输层协议包括TCP和UDP。 1. TCP 1.1. 首部格式 1.2. TCP状态机 TCP是面向连接的,在其生命周期会有各种不同状态 | 状态 | 描述 | | ------------ | ---------------------...","relativePath":"Tech/Network/传输层/TCP与UDP协议.md","rawContent":"---\r\ntitle: 计算机网络(九)—— 传输层\r\ndate: 2020-04-27\r\ncategories:\r\n- 计算机网络\r\ntags:\r\n- 计算机网络\r\n---\r\n传输层架构在网络层之上,在两台计算机**进程**之间传输数据,常见的传输层协议包括TCP和UDP。\r\n# 1. TCP\r\n\r\n## 1.1. 首部格式\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200423212128.png)\r\n\r\n## 1.2. TCP状态机\r\n\r\nTCP是面向连接的,在其生命周期会有各种不同状态\t\r\n\r\n| 状态 | 描述 |\r\n| ------------ | ----------------------------------------------- |\r\n| LISTEN | 等待来自远程TCP应用程序的请求 |\r\n| SYN_SENT | 发送连接请求后等待来自远程端点的确认。TCP第一次握手后客户端所处的状态 |\r\n| SYN-RECEIVED | 该端点已经接收到连接请求并发送确认。该端点正在等待最终确认。TCP第二次握手后服务端所处的状态 |\r\n| ESTABLISHED | 代表连接已经建立起来了。这是连接数据传输阶段的正常状态 |\r\n| FIN_WAIT_1 | 等待来自远程TCP的终止连接请求或终止请求的确认 |\r\n| FIN_WAIT_2 | 在此端点发送终止连接请求后,等待来自远程TCP的连接终止请求 |\r\n| CLOSE_WAIT | 该端点已经收到来自远程端点的关闭请求,此TCP正在等待本地应用程序的连接终止请求 |\r\n| CLOSING | 等待来自远程TCP的连接终止请求确认 |\r\n| LAST_ACK | 等待先前发送到远程TCP的连接终止请求的确认 |\r\n| TIME_WAIT | 等待足够的时间来确保远程TCP接收到其连接终止请求的确认 |\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200423203536.png)\r\n### 1.2.1. 三次握手\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200423210040.png)\r\n\r\n### 1.2.2. 四次挥手\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200423210106.png)\r\n具体过程如下:\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241229221623113.png)\r\n- 客户端主动调用关闭连接的函数,于是就会发送FIN报文,这个FIN报文代表客户端不会再发送数据了,进入FIN_WAIT_1状态;\r\n- 服务端收到了FIN报文,然后马上回复一个ACK确认报文,此时服务端进入 CLOSE_WAIT状态。在收到FIN报文的时候,TCP 协议栈会为FIN包插入一个文件结束符EOF到接收缓冲区中,服务端应用程序可以通过read调用来感知这个FIN包,这个EOF会被放在已排队等候的其他已接收的数据之后,所以必须要得继续read接收缓冲区已接收的数据;\r\n- 接着,当服务端在read数据的时候,最后自然就会读到EOF,接着read() 就会返回 0,这时服务端应用程序如果有数据要发送的话,就发完数据后才调用关闭连接的函数,如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,这时服务端就会发一个FIN包,这个FIN报文代表服务端不会再发送数据了,之后处于LAST_ACK状态;\r\n- 客户端接收到服务端的FIN包,并发送ACK确认包给服务端,此时客户端将进入TIME_WAIT状态;\r\n- 服务端收到ACK确认包后,就进入了最后的CLOSE状态;\r\n- 客户端经过2MSL时间之后,也进入CLOSE状态;\r\n\r\n![[tcp关键内核参数#1.1. tcp timewait]]\r\n\r\n### 1.2.3. RST\r\n其实关闭的连接的函数有两种函数:\r\n- close函数,同时socket关闭发送方向和读取方向,也就是socket不再有发送和接收数据的能力;\r\n- shutdown函数,可以指定socket只关闭发送方向而不关闭读取方向,也就是socket 不再有发送数据的能力,但是还是具有接收数据的能力;\r\n\r\nclose在某些场景下会导致RST:\r\n1. 只要TCP栈的读缓冲里还有未读取(read)数据,则调用close时会直接向对端发送RST。比如客户端一次发送100个字节,但是服务器read设置最多读取90个,read一次后就不再read,所以还有10个在缓冲区,服务器执行close,服务器会发送RST。如果客户端第一次发送100个字节,服务器read最多读取100个,read一次把数据全部读出来了,然后客户端再发送100个字节,但是服务器没有read,执行close,执行的是四次挥手。\r\n2. 如果客户端用close函数来关闭连接,那么在TCP四次挥手过程中,如果收到了服务端发送的数据,由于客户端已经不再具有发送和接收数据的能力,所以客户端的内核会回RST报文给服务端,然后内核会释放连接,这时就不会经历完成的 TCP 四次挥手,所以我们常说,调用close是粗暴的关闭。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241229222110694.png)\r\n\r\n当服务端收到RST后,内核就会释放连接,当服务端应用程序再次发起读操作或者写操作时,就能感知到连接已经被释放了\r\n- 如果是读操作,则会返回RST的报错,也就是我们常见的[[http连接池#2.1.2. Connection reset by peer|Connection reset by peer]]。\r\n- 如果是写操作,那么程序会产生SIGPIPE信号,应用层代码可以捕获并处理信号,如果不处理,则默认情况下进程会终止,异常退出。\r\n相对的,shutdown函数因为可以指定只关闭发送方向而不关闭读取方向,所以即使在 TCP 四次挥手过程中,如果收到了服务端发送的数据,客户端也是可以正常读取到该数据的,然后就会经历完整的 TCP 四次挥手,所以我们常说,调用 shutdown 是优雅的关闭。\r\n- 当调用 `shutdown(sockfd, SHUT_RD)` 时,关闭套接字的读方向,这意味着该套接字不能再接收数据,即使接收缓冲区中还有数据也会被丢弃。对端如果继续发送数据,接收方会回复 RST 包,强制关闭连接。\r\n- 当调用 `shutdown(sockfd, SHUT_WR)` 时,关闭套接字的写方向,这会导致发送缓冲区中的数据被立即发送出去(如果有数据的话),然后发送一个 FIN 包给对端,通知对端本方不再发送数据,但仍可以接收对端的数据。这就是所谓的 “半关闭” 状态,常用于在完成数据发送后,等待对方发送完剩余数据再完全关闭连接的场景。\r\n\t- 当调用 `shutdown(sockfd, SHUT_RDWR)` 时,相当于同时关闭读写方向,功能类似 `close` 函数,但 `shutdown` 不会立即释放套接字资源,直到所有数据传输完毕和连接完全关闭。\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241229222354006.png)\r\n\r\n#### 1.2.3.1. 四次挥手优化为三次\r\n当被动关闭方(下图的服务端)在 TCP 挥手过程中,「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241229220836128.png)\r\n因为TCP延迟确认机制是默认开启的,所以导致我们抓包时,看见三次挥手的次数比四次挥手还多\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241229220936995.png)\r\n\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241229220909787.png)\r\n## 1.3. TCP延迟确认机制\r\n当发送没有携带数据的ACK,它的网络效率也是很低的,因为它也有40个字节的IP头和TCP头,但却没有携带数据报文。为了解决ACK传输效率低问题,所以就衍生出了TCP延迟确认。\r\n\r\nTCP 延迟确认的策略:\r\n- 当有响应数据要发送时,ACK会随着响应数据一起立刻发送给对方\r\n- 当没有响应数据要发送时,ACK将会延迟一段时间,以等待是否有响应数据可以一起发送\r\n- 如果在延迟等待发送ACK期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20241229221203486.png)\r\n\r\n\r\n# 2. UDP\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200423225822.png)\r\n\r\n# 3. TCP与UDP的区别\r\n\r\n| | TCP | UDP |\r\n| -------- | ---------------------------------------------- | ------------------------------------- |\r\n| 是否连接 | 面向无连接 | 面向连接 |\r\n| 是否可靠 | 不可靠传输,尽力交付 | 可靠传输,有流量控制和拥塞控制 |\r\n| 通信方式 | 支持单播、多播 | 只能点对点通信 |\r\n| 传输方式 | 面向报文 | 面向字节流 |\r\n| 首部开销 | 8字节 | 最小20字节,最大60字节 |\r\n| 使用场景 | 适用IP电话、视频会议、直播、即时通讯等实时应用 | 适用要求可靠传输的应用,如http,ftp等 |\r\n\r\n# 4. 引用\r\n1. [TCP状态机](https://www.jianshu.com/p/3c7a0771b67e)\r\n2. [TCP报文段首部格式详解](https://blog.csdn.net/wilsonpeng3/article/details/12869233)\r\n3. [TCP四次挥手变为三次](https://www.51cto.com/article/717235.html)"},{"id":"内部路由协议","title":"路由协议","date":"2020-04-26T00:00:00.000Z","tags":["network"],"readingTime":23,"slug":"内部路由协议","description":"路由是选择路径并将报文沿着选择的路径进行转发的过程。 1. 路由器 1.1. 路由器功能 路由器从功能上可以划分为: - 路由选择:使用路由协议(或手工静态配置的方式)获取整个网络的拓扑结构,构造、维护路由表。 - 分组转发:分组转发结构由交换结构、一组输入端口和一组输出端口构成。 1. IP分组检...","relativePath":"Tech/Network/路由协议/内部路由协议.md","rawContent":"---\r\ntitle: 路由协议\r\ndate: 2020-04-26\r\ncategories:\r\n- network\r\ntags:\r\n- network\r\n---\r\n路由是选择路径并将报文沿着选择的路径进行转发的过程。\r\n\r\n \r\n\r\n# 1. 路由器\r\n## 1.1. 路由器功能\r\n路由器从功能上可以划分为:\r\n- 路由选择:使用路由协议(或手工静态配置的方式)获取整个网络的拓扑结构,构造、维护路由表。\r\n- 分组转发:分组转发结构由交换结构、一组输入端口和一组输出端口构成。\r\n\t1. IP分组检查:检查版本号,检查分组头字段,计算头校验和。\r\n\t2. 目的IP地址分析与路由表查找:决定分组的输出接口和到达目的IP地址的下一跳节点。\r\n\t3. 分组TTL控制:路由器调整TTL值字段,防止分组在网络中无终止循环。本地递交的分组TTL值要大于0。对于向外转发的分组,首先TTL值减去1,在实际转发之前还要重新检查TTL值。TTL值过期的分组要丢掉,同时还可能向分组的发送者通告错误信息。\r\n\t4. 校验和计算:TTL字段的变化,要求重新计算校验和。\r\n\t5. IP分片:为了适应输出网络接口的MTU(Maximum Transmission Unit)值,有时需要分片处理。分片对性能的影响比较大,现在由于PMTU技术的应用,分片操作并不多见。\r\n\r\n> 对于IPv4包,路径MTU发现通过在传出包的IP头中设置Don't Fragment (DF)标志位来工作。然后,任何路径上MTU小于数据包的设备都将丢弃它,并返回包含其MTU过大的ICMPv4(类型3、代码4)数据包,从而允许源主机适当地减小其路径MTU。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427143715.png)\r\n## 1.2. 路由分组转发过程\r\n1. 从数据报的首部提取目的主机的IP地址D,得到目的网络地址N\r\n2. 若N就是与此路由器直接相连的某个网络地址,则进行直接交付\r\n3. 若路由表中有目的地址为D的特定主机路由,则把数据报传送给表中所指明的下一跳路由器\r\n4. 若路由表中有到达网络N的路由,则把数据报传送给路由表中所指明的下一跳路由器\r\n5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器\r\n6. 报告转发分组出错\r\n\r\n> 路由选择使用CIDR技术,通过最长前缀匹配来定位最精准的一条路由信息。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427145443.png)\r\n\r\n# 2. 路由协议\r\n路由选择协议都是自适应的,能随着网络通信量和拓扑结构的变化而自适应地进行调整。互联网可以划分为许多较小的自治系统(Autonomous system, AS),不同的AS可以采用不同的路由选择协议。路由选择协议主要可以分为两大类:\r\n- 内部网关协议(Interior Gateway Protocol,IGP):RIP、OSPF和IS-IS\r\n- 外部网关协议(Exterior Gateway Protocol,EGP):BGP\r\n## 2.1. 路径算法\r\n路由算法的本质是寻找最短路径,最短的含义取决于对链路长度的定义。长度通常是一个正数,它可以是物理距离的长短、时延的大小、各个节点队列长度等等。如果长度取1,则最短路由即为最小跳数 (中转次数)的路由。其次,链路的长度随着时间可能是变化的,它取决于链路拥塞情况。路由中使用最大的是Dijkstra算法,其是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。\r\n## 2.2. 内部网关协议IGP\r\n### 2.2.1. RIP\r\n#### 2.2.1.1. 简介\r\nRIP (Routing Information Protocol,路由信息协议)是一种**基于距离向量**的路由选择协议。距离是指跳数,设备到与他直连网络的设备跳数为0,然后每经过一个**三层**设备跳数增加1,也就是度量值等于从本网络到达目网络间的三层设备数量。跳数最多为 15,超过 15 表示不可达。\r\n\r\n由于RIP的实现较为简单,在配置和维护管理方面也远比OSPF和IS-IS容易,因此RIP主要应用于规模较小的网络中,例如校园网以及结构较简单的地区性网络。对于更为复杂的环境和大型网络,一般不使用RIP协议。RIP通过**UDP报文**进行路由信息的交换,使用的端口号为520。所以它又是一个不可靠的路由协议。\r\n#### 2.2.1.2. 更新机制\r\nRIP协议有两种更新机制:定期更新和触发更新。定期更新是根据设置的更新计时器定期发送RIP路由通告。而触发更新是RIP路由器一旦察觉到网络变化,就尽快甚至是立即发送更新报文,而不等待更新周期结束。只要触发更新的速度足够快,就可以大大地防止“计数到无穷大”的发生,但是这一现象还是有可能发生的,详见[[内部路由协议#^af58f6|RIP环路问题]]。\r\n\r\n两种更新机制都遵循相同的更新规则:\r\n1. 如果更新的某路由表项在路由表中没有,则直接在路由表中添加该路由表项\r\n2. 如果路由表中已有相同目的网络的路由表项,且来源端口相同,那么无条件根据最新的路由信息更新其路由表\r\n3. 如果路由表中已有相同目的网络的路由表项,但来源端口不同,则要比较它们的跳数,将跳数较小的一个作为自己的路由表项\r\n4. 如果路由表中已有相同目的网络的路由表项,且跳数相等,保留原来的路由表项\r\n5. 若 3 分钟还没有收到相邻路由器的更新路由表,则把该相邻路由器标为不可达,即把距离置为 16\r\n\r\n#### 2.2.1.3. 示例\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427153405.png)\r\n\r\n \r\n1. 所有路由器中的路由表都只有和自己直接连接的网络的路由表项。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427154311.png)\r\n2. 各路由器就会按设置的周期(默认为30秒)向相邻路由器发送路由更新。具体哪个路由器会先发送路由更新,取决于哪个路由器先开。现假设路由器R2先收到来自路由器R1和R3的路由更新,然后就更新自己的路由表,添加了分别通过R1和R3到达10.0.0.0/8网络和13.0.0.0/8网络的路由表项,跳数加1。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427154917.png)\r\n3. R2更新自己的路由表后,会把完整的路由表发给邻居路由器R1和R3。路由器R1和R3分别再进行更新。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427155154.png)\r\n4. 这个过程会按照固定时间间隔不断更新,最终路由表会趋向于收敛\r\n\r\n### 2.2.2. OSPF\r\n#### 2.2.2.1. 简介\r\nOSPF(Open Shortest Path First,开放式最短路径优先)是大中型网络上使用较为广泛的内部网关协议,使用“代价(Cost)”作为路由度量。OSPF采用Dijkstra算法(SPF算法)来计算最短路径树。与RIP和BGP不同的是,OSPF协议不使用TCP或者UDP协议而是直接承载在IP协议之上。OSPF解决RIP存在的三个典型问题:\r\n1. **收敛慢**:全局维护LSDB,独立生成SPF树\r\n2. **易产生路由环路**:spf算法保证\r\n3. **可扩展性差**:最大只能支持15跳\r\n此外OSPF还具有以下优点:\r\n1. 采用组播形式收发报文,减少对其他不运行ospf路由器的影响。\r\n2. 支持无类型域间选路(cidr)\r\n3. 支持等价路由进行负载分担\r\n4. 支持报文加密\r\n#### 2.2.2.2. Cost代价\r\n每个路由器都把自己当做Dijkstra算法的根,通过累计cost来计算达到目的地的最短路径。默认情况下,路由器根据接口的配置带宽来计算cost,带宽越高,开销越低,计算公式如下。最终cost是对路径上每一个路由器的入接口的cost值累加得到的。\r\n$\r\ncost_i = B_c/B_i,其中B_c为参照带宽10^8bit/s,B_i为路由器i的入接口带宽\r\n$\r\n#### 2.2.2.3. 工作过程\r\n1. 每台路由器和直接相连的路由器互交,发送Hello报文,建立邻居关系。\r\n2. 每台路由器构建包含直接相连的链路状态的LSA(Link-State Advertisement,链路状态通告)。链路状态通告(LSA)中记录了所有相关的路由器,包括路由器接口状态、接口IP地址、掩码、链路类型、带宽等。\r\n3. 每台路由器泛洪链路状态通告(LSA)给所有的邻路由器,并且自己也在本地储存邻路由发过来的LSA,然后再将收到的LSA泛洪给自己的所有邻居,直到在同一区域中的所有路由器收到了所有的LSA。每台路由器在本地数据库中保存所有收到的LSA副本,这个数据库被称作\"链路状态数据库(LSDB,Link-State Database)\"。\r\n4. 每台路由器基于本地的\"链路状态数据库(LSDB)\"执行\"最短路径优先(SPF)\"算法,并以本路由器为根,生成一个SPF树。\r\n5. 基于SPF树计算去往每个网络的最短路径,得到最终的路由表。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427200104.png)\r\n\r\n> 路由器通过LSA的交换,最后独立的计算出到每个网络的最短路径,相对RIP具有更强的全局观,收敛速度更快。当检测到拓扑发生变化时立即发送更新,链路状态实时性更强。但SPF算法复杂度更高,对内存需求高,同时在网络初始化时,大量链路状态包泛洪,会影响网络的可用带宽。\r\n\r\n#### 2.2.2.4. LSA\r\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/ospf_lsa.png)\r\n\r\n#### 2.2.2.5. 区域\r\n一个OSPF网络分为多个区域。区域将网络中的路由器在逻辑上分组并以区域为单位向网络的其余部分发送汇总路由信息。区域是以接口(Interface)为单位来划分的,所以一台多接口路由器可能属于多个区域。相同区域内的所有路由器都维护一份相同的链路状态数据库(LSDB),如果一台路由器属于多个区域,那么它将为每一个区域维护一份LSDB。常见的区域类型有如下几种:\r\n1. 骨干区域:骨干区域(area0)是整个OSPF的核心区域。所有其他非骨干区域必须和骨干区域直接或间接相连。骨干区域的功能是在不同的非骨干区域之间分发路由信息。骨干区域必须是唯一而且连续的,但不需要在物理上连续,可以使用虚拟连接。\r\n2. 传送区域:传送区域连接至少2个其他区域,它将通告从一个区域传送到另一个区域。\r\n3. 末梢区域:末梢区域是一个不允许AS外部路由通告(AS External LSA,即类型5 LSA)在其内部进行洪泛扩散的区域。末梢区域的区域边界路由器会通告一条默认路由(default route)到该区域内所有路由器,任何发往AS外部网络的数据流都将依据默认路由来转发。\r\n \r\n将一个网络划分为多个区域有以下优点:\r\n- 某一区域内的路由器只需要维护该区域的LSDB,而不用维护整个OSPF网络的链路状态数据库。\r\n- 将某一区域网络拓扑变化的影响限制在该区域内,不会影响到整个OSPF网络,从而减小OSPF计算的频率。\r\n- 将链路状态通告(LSA)的洪泛限制在本区域内,从而降低OSPF协议产生的数据量。\r\n- 划分区域可以对网络进行层次化结构设计。\r\n- 划分区域有利于资源合理调配,核心区域部署性能较好的设备资源,边缘区域部署性能较差的设备资源即可。\r\n#### 2.2.2.6. 路由器类型\r\n1. 内部路由器(IR, internal router):一台路由器上所有启动了OSPF的接口都在同一区域。\r\n2. 骨干路由器(BBR, backbone router):骨干路由器是指至少有一个启用了OSPF的接口是和骨干区域相连的路由器。骨干路由器也可以同时是区域边界路由器或自治系统边界路由器。\r\n3. 区域边界路由器(ABR, area border router):区域边界路由器是指连接一个或者多个区域的路由器。区域边界路由器为每一个与之相连的区域维护一份链路状态数据库,因此区域边界路由器需要比内部路由器更多的内存资源和更高性能的处理器。\r\n4. 自治系统边界路由器: 自治系统边界路由器用来把从其他路由协议学习到的路由以路由重分发的方式注入到OSPF进程中,从而使得整个OSPF域内的路由器都可以学习到这些路由(除了末梢区域内的路由器)。一台自治系统边界路由器可以是OSPF域内非末梢区域的任何路由器,它可以是内部路由器、区域边界路由器、骨干路由器。\r\n## 2.3. 外部网关协议BGP\r\n\r\n### 2.3.1. 路由注入和通告\r\nBGP路由器的路由注入和通告都是为了修改BGP路由表。当路由器之间建立BGP邻居之后,就可以相互交换BGP路由。一台运行了BGP协议的路由器,会将BGP得到的路由与普通路由分开存放,所以BGP路由器会同时拥有两张路由表:\r\n- IGP路由表:通过`show ip route`查看,存放通过IGP协议或手工配置得到的普通路由表\r\n- BGP路由表:通过`show ip bgp`查看,运行BGP之后创建的路由表。BGP路由表的路由信息只能传递给BGP协议,如果两台BGP邻居的BGP路由表为空,就不会有任何路由传递。\r\n#### 2.3.1.1. BGP路由表注入\r\n- 纯动态注入:路由器将通过IGP路由协议动态获得的路由信息直接注入到BGP中。\r\n- 半动态注入:路由器有选择的将IGP路由协议获得的动态路由信息注入到BGP中。\r\n- 静态注入: 路由器将静态配置的某条路由注入到BGP系统中。\r\n#### 2.3.1.2. BGP路由通告\r\n在BGP路由表注入路由后,BGP路由器之间会将这些路由在BGP路由器间进行**通告**。通告要遵守以下规则:\r\n- BGP 路由器只把自己使用的路由通告给相邻体\r\n- BGP 路由器从EBGP获得的路由会向它的所有BGP相邻体通告(包括EBGP和IBGP)\r\n- BGP路由器从IBGP获得的路由不会向它的IBGP相邻体通告(避免内部产生环路)\r\n- BGP 路由器从IBGP获得的路由是否通告给它的EBGP相邻体要依IGP和BGP同步的情况而定\r\n#### 2.3.1.3. 路径属性\r\n在默认情况下,到达同一目的地,BGP只走单条路径,并不会在多条路径之间执行负载均衡。对于IGP路由协议,当有多条路径可以到达同一目的地时,则根据最小metric值来选择最优路径,而 BGP 存在多条路径到达同一目的地时,对于最优路径的选择,BGP并不会以metric值大小为依据,BGP对于最优路径的选择,需要靠比较路由条目中的路径属性,只有在比较多条路由的属性之后,才能决定选择哪条为最优路径。\r\n\r\nBGP的路径属性可以划分为以下四类:\r\n- 公认强制 (Well-Known Mandatory):所有的路由中都需要写入公认强制属性。如Origin,Next Hop,AS path。\r\n- 公认自选 (Well-Known Discretionary):能够理解和支持即可,不一定要写入路由\r\n- 可选可传递 (Optional Transitive):不一定要理解或支持\r\n- 可选不可传递(Optional Nontransitive):只有特定的BGP路由器才能理解和传递\r\n\r\n对于任何一台运行BGP的路由器,都必须支持公认强制属性,并且在将路由信息发给其它BGP邻居时,必须在路由中写入公认强制属性,这些属性是被强制写入路由中的,一条不带公认强制属性的路由被BGP路由器被视为无效而被丢弃,一个不支持公认强制属性的BGP,是不正常的,不合法的BGP。\r\n#### 2.3.1.4. rigin\r\norigin属性为起源属性,描述路由是以何种方式注入到BGP路由表中的,主要有以下两种情况:\r\n- 以 `network` 命令注入到BGP路由表中,origin 属性为 IGP\r\n- 以 `redistribute` 命令注入到BGP路由表中,origin 属性为 Incomplete\r\n\r\n> IGP优先级比Incomplete高\r\n#### 2.3.1.5. Next Hop\r\n指示下一个AS的路由器入口的网段,同一个AS内Next hop的值不变\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427220030.png)\r\n#### 2.3.1.6. As Path\r\n描述了该路由经过的AS组成的路径,AS路径中不能算上自己的AS,从离自己最近的AS开始,以目的网络的AS结束。下图为 AS5 到 AS1 的路由的AS Path属性:\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200427220121.png)\r\n\r\n 借助路由的AS Path属性,可以避免环路,具体操作就是收到一条AS Path属性中含有自己AS的路由的时候丢弃该路由。在选路的时候,优先选As Path最短的那条,如果As Path距离相等,则优选本AS内到出口路由器最短的那根,如果还相等,则选择Router_ID(发送路由的路由器)最小的那根。\r\n### 2.3.2. BGP的缺陷与解决方法\r\n \r\n# 3. 引用\r\n1. [RIP环路问题总结](https://blog.51cto.com/566577/197797)\r\n ^af58f6\r\n2. [RIP路由协议](https://www.jianshu.com/p/f542d5b415a1)\r\n3. [OSPF协议详解](https://www.qingsword.com/qing/596.html)\r\n4. [BGP漫谈](https://zhuanlan.zhihu.com/p/25433049)\r\n5. [计算机网络课程总结--BGP协议](http://wulc.me/2016/12/25/计算机网络课程总结--BGP协议/)\r\n6. [【网络干货】超全ISIS路由协议技术详解 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/126754100)\r\n7. [什么是 BGP?| BGP 路由说明](https://www.cloudflare.com/zh-cn/learning/security/glossary/what-is-bgp/)\r\n8. [【网络干货】最全BGP路由协议技术详解 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/126754314)"},{"id":"ICMP","title":"计算机网络(七)—— 网络层 —— ICMP","date":"2020-04-25T00:00:00.000Z","tags":["network"],"readingTime":7,"slug":"icmp","description":"互联网控制消息协议(Internet Control Message Protocol,ICMP)是TCP/IP协议族的核心协议之一,用于IP协议中发送控制消息,提供可能发生在通信环境中的各种问题反馈。通过这些消息,使管理者可以对所发生的的问题作出诊断,然后采取适当的措施解决。ICMP可以简单认为是...","relativePath":"Tech/Network/网络层/ICMP.md","rawContent":"---\ntitle: 计算机网络(七)—— 网络层 —— ICMP\ndate: 2020-04-25\ncategories:\n- network\ntags:\n- network\n---\n\n互联网控制消息协议(Internet Control Message Protocol,ICMP)是TCP/IP协议族的核心协议之一,用于IP协议中发送控制消息,提供可能发生在通信环境中的各种问题反馈。通过这些消息,使管理者可以对所发生的的问题作出诊断,然后采取适当的措施解决。ICMP可以简单认为是网络环境的DEBUG工具。\n\n\n\n# 1. ICMP报文格式\nICMP是基于IP协议工作的,属于网络层协议。ICMP报文包含在IP数据报中,IP报头在ICMP报文的最前面。当IP报头中的协议字段值为1时,就说明这是一个ICMP报文。ICMP报头从IP报头的第160位开始(IP首部20字节,IP报头使用了可选字段除外)。\n\n![image.png](https://shinerio.oss-cn-beijing.aliyuncs.com/obsidian/icmp%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F.png)\n\n- Type:ICMP类型\n- Code:进一步划分ICMP类型,用来查找产生错误的原因\n- 校验码:通过ICMP报头和数据部分计算得来\n\n ICMP的报文类型有很多种,具体可参考[Wikipedia](https://zh.wikipedia.org/wiki/互联网控制消息协议)。\n \n# 2. ping\n我们用的ping操作中就包括了请求(类型字段值为8)和应答(类型字段值为0)ICMP报文。一台主机向一个节点发送一个类型字段值为8(请求)的ICMP报文,如果途中没有异常(没有被路由丢弃,目标不回应ICMP或者传输失败),则目标返回类型字段值为0(应答)的ICMP报文,说明这台主机存在。一个ping报文如下\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200426202102.png)\n\n## 2.1. ping响应类型\n### 2.1.1. 正常\n```text\n# 请求1\nInternet Control Message Protocol\nType: 8 (Echo (ping) request)\nCode: 0\nChecksum: 0x629d [correct]\n[Checksum Status: Good]\nIdentifier (BE): 7509 (0x1d55)\nIdentifier (LE): 21789 (0x551d)\nSequence number (BE): 0 (0x0000)\nSequence number (LE): 0 (0x0000)\n[Response frame: 34]\nTimestamp from icmp data: Apr 26, 2020 20:40:28.240933000 CST\n[Timestamp from icmp data (relative): 0.000056000 seconds]\nData (48 bytes)\n\n# 响应1\nInternet Control Message Protocol\nType: 0 (Echo (ping) reply)\nCode: 0\nChecksum: 0x6a9d [correct]\n[Checksum Status: Good]\nIdentifier (BE): 7509 (0x1d55)\nIdentifier (LE): 21789 (0x551d)\nSequence number (BE): 0 (0x0000)\nSequence number (LE): 0 (0x0000)\n[Request frame: 31]\n[Response time: 29.529 ms]\nTimestamp from icmp data: Apr 26, 2020 20:40:28.240933000 CST\n[Timestamp from icmp data (relative): 0.029585000 seconds]\nData (48 bytes)\n\n# 请求2\nInternet Control Message Protocol\nType: 8 (Echo (ping) request)\nCode: 0\nChecksum: 0x514a [correct]\n[Checksum Status: Good]\nIdentifier (BE): 7509 (0x1d55)\nIdentifier (LE): 21789 (0x551d)\nSequence number (BE): 1 (0x0001)\nSequence number (LE): 256 (0x0100)\n[Response frame: 50]\nTimestamp from icmp data: Apr 26, 2020 20:40:29.245366000 CST\n[Timestamp from icmp data (relative): 0.000148000 seconds]\nData (48 bytes)\n\n# 响应2\nInternet Control Message Protocol\nType: 0 (Echo (ping) reply)\nCode: 0\nChecksum: 0x594a [correct]\n[Checksum Status: Good]\nIdentifier (BE): 7509 (0x1d55)\nIdentifier (LE): 21789 (0x551d)\nSequence number (BE): 1 (0x0001)\nSequence number (LE): 256 (0x0100)\n[Request frame: 49]\n[Response time: 33.374 ms]\nTimestamp from icmp data: Apr 26, 2020 20:40:29.245366000 CST\n[Timestamp from icmp data (relative): 0.033522000 seconds]\nData (48 bytes)\n```\n\n### 2.1.2. 超时\n\n```\nRequest timeout for icmp_seq 0\nRequest timeout for icmp_seq 1\n```\n\n有以下几种情况可能会导致超时\n- 对方已关机或ip地址根本不存在\n- 对方与自己不在同一网段内,通过路由也无法找到对方\n- 对方确实存在,但设置了ICMP数据包过滤\n\n### 2.1.3. Destination host Unreachable\n\n- 对方与自己不在同一网段内,而自己又未设置默认的路由\n- 网线出了故障\n\n> destination host unreachable和time out的区别在于如果所经过的路由器的路由表中具有到达目标的路由,而目标因为其他原因不可到达,这时候会出现“time out”,如果路由表中连到达目标的路由都没有,那就会出现“destination host unreachable”。\n\n### 2.1.4. Bad IP address\n这个信息表示可能没有连接到DNS服务器,所以无法解析这个IP地址,也可能是IP地址不存在。\n\n### 2.1.5. Source quench received\n这个信息比较特殊,它出现的机率很小。它表示对方或中途的服务器繁忙无法回应。\n\n### 2.1.6. Unknown host\n这种出错信息的意思是,该远程主机的名字不能被域名服务器(DNS)转换成IP地址。故障原因可能是:\n- 域名服务器有故障\n- 域名不存在,无法被解析\n- 网络管理员的系统与远程主机之间的通信线路有故障。\n\n### 2.1.7. No answer\n这种故障说明本地系统有一条通向中心主机的路由,但却接收不到它发给该中心主机的任何信息。故障原因可能是下列之一:\n- 中心主机没有工作;\n- 本地或中心主机网络配置不正确;\n- 本地或中心的路由器没有工作;\n- 通信线路有故障;\n- 中心主机存在路由选择问题。\n\n### 2.1.8. no rout to host\n网卡工作不正常。\n\n### 2.1.9. transmit failed,error code:10043\n网卡驱动不正常。\n\n### 2.1.10. unknown host name\nDNS配置不正确。\n\n### 2.1.11. 不产生ICMP错误报文的情况\n1. 对ICMP差错报文,不再发送ICMP差错报告报文\n2. 对第一个分片的数据报片的所有后续数据报片,都不发送ICMP差错报告报文\n3. 对具有多播地址的数据报,都不发送ICMP差错报告报文\n4. 对具有特殊地址(如127.0.0.0或0.0.0.0)的数据报,不发送ICMP差错报告报文\n\n## 2.2. IP记录路由\nping使用IP首部的选项字段来记录IP报文经过了哪些主机,IP首部中的选项字段最长40字节,可以使用`ping -R`选项开启记录。\n\n# 3. traceroute\n受ip头选项长度的限制,ping不能完全的记录下所经过的路由器,而traceroute是用来检测主机到目的主机之间所经路由情况的重要工具。\n\ntraceroute的原理如下:\n1. 首先给目的主机发送一个TTL=1的UDP数据包,而经过的第一个路由器收到这个数据包以后,就自动把TTL减1,而TTL变为0以后,路由器就把这个包给抛弃了,并同时产生一个主机不可达的ICMP数据报给主机。\n2. 主机收到这个数据报以后再发一个TTL=2的UDP数据报给目的主机,然后触发第二个路由器给主机发ICMP数据报。\n3. 如此往复直到到达目的主机。这样,traceroute就拿到了所有的路由器ip。从而避开了ip头只能记录有限路由IP的问题。\n\n> 源主机如何知道UDP到没到达目的主机呢?这就涉及一个技巧的问题,普通的网络程序只监控少数的几个号码较小的端口,比如说80,23等等。而traceroute发送的是端口号>30000的UDP报,所以到达目的主机的时候,目的主机只能发送一个端口不可达的ICMP数据报给主机。由此,源主机就可以知道数据包到达了目标主机。"},{"id":"数据链路层概述","title":"数据链路层概述","date":"2020-04-22T00:00:00.000Z","tags":["计算机网络"],"readingTime":24,"slug":"数据链路层概述","description":"数据链路层使用的信道主要分为以下两种: - 点对点信道,使用一对一的点对点的通信方式 - 广播信道,使用一对多的广播通信方式,广播信道上连接的主机很多,因此必须使用专用的共享信道协议来协调这些主机的数据发送。 0.1. 数据链路 当需要在一条线路上传送数据时,除了必须有一条物理线路外,还必须有一些必...","relativePath":"Tech/Network/数据链路层/数据链路层概述.md","rawContent":"---\r\ntitle: 数据链路层概述\r\ndate: 2020-04-22\r\ncategories:\r\n - 计算机网络\r\ntags:\r\n - 计算机网络\r\n---\r\n数据链路层使用的信道主要分为以下两种:\r\n- 点对点信道,使用一对一的点对点的通信方式\r\n- 广播信道,使用一对多的广播通信方式,广播信道上连接的主机很多,因此必须使用专用的共享信道协议来协调这些主机的数据发送。\r\n## 0.1. 数据链路\r\n当需要在一条线路上传送数据时,除了必须有一条物理线路外,还必须有一些必要的通信协议来控制这些数据的传输。若把实现这些协议的硬件和软件加到链路上,就构成了数据链路。现在最常用的方法是使用**网络适配器**来实现这些协议。一般适配器都包括了**数据链路层**和**物理层**这两层功能。\r\n\r\n网络适配器的功能包括:\r\n1. **数据封装** 网络适配器的主要功能之一是数据封装。当计算机需要发送数据时,适配器会接收来自高层协议的数据,并在数据前面添加物理层的头部信息,构成完整的数据帧。这个过程称为数据封装。\r\n2. **数据编码和解码** 适配器还负责将数字数据编码成可在网络介质上传输的形式,如通过调制将数据编码为电子信号。接收数据时,适配器会对接收到的编码过的信号进行解码,恢复出原始数字数据。\r\n3. **介质访问控制** 当多台设备同时尝试在共享介质(如以太网)上发送数据时,就会出现冲突。适配器利用介质访问控制(MAC)机制来协调设备对网络的访问,避免冲突。常见的MAC机制有CSMA/CD(载波监听多路访问/冲突检测)。PS:现代交换机的出现已经不会出现冲突。\r\n4. **地址识别** 每个网络适配器都有一个唯一的物理地址(MAC地址),用于在局域网中识别和定位设备。适配器会检查接收到的数据帧的目标MAC地址,只接收发往自己的数据,避免不必要的处理。\r\n5. **错误检测和纠正** 适配器通常会对接收到的数据进行循环冗余检验(CRC),检测数据在传输过程中是否发生错误。某些适配器还支持纠正少量的比特错误。\r\n\r\n## 0.2. 基本问题\r\n\r\n数据链路层使用物理层提供的服务在通信信道上发送和接收比特。数据链路层解决的主要问题包括:\r\n- 封装成帧\r\n- 透明传输\r\n- 差错控制\r\n\r\n\r\n\r\n### 0.2.1. 封装成帧\r\n\r\n封装成帧(帧同步)就是在将网络层的IP数据报的前后分别添加首部和尾部。不同数据链路层协议的帧首部和尾部包含的信息有明确规定,帧的首部和尾部有帧开始符和帧结束符,称之为帧界定符。每一种数据链路层协议都规定了所能够传输的帧的数据部分的上限,即MTU(Maximum Transmission Unit),以太网的MTU为1500字节。\r\n\r\n> 1500字节是考虑到传输效率以及传输时间而折中选择的一个值\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200428143139.png)\r\n\r\n### 0.2.2. 透明传输\r\n透明表示一个实际存在的事物看起来好像不存在一样,该层上传输的数据的内容、格式及编码没有限制,也没有必要解释信息结构的意义。\r\n\r\n比如使用SOH(Start Of Header)--0x01和EOT(End Of Transmission)--0x04来表示,这样数据链路层就可以识别出帧的开始和结束。如果数据部分出现SOH或EOT,则采用字节填充的方式,比如在SOH和EOT前面分别插入一个转义字符ESC--0x1B,在接收端的数据链路层将数据交递给网络层之前删除这个插入的转义字符。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200428152202.png)\r\n\r\n### 0.2.3. 差错控制\r\n\r\n现实的通信链路并不是理想的,比特在传输过程中可能会产生差错(1变0,0变1),这就叫做比特差错。为了保证数据传输的可靠性,在计算机网络传输数据时,必须采用各种差错检测措施。目前在数据链路层广泛使用了循环冗余检验CRC(Cyclic Redundancy Check)的差错检验技术来判定一帧是否在传输过程中发生了错误,一旦发生错误就可以采用反馈重传的方法来纠正。发送数据前先计算帧的数据部分得出FCS添加在数据部分后,接收端收到数据后进行同样的CRC计算与接收到的FCS字段进行比较,判断数据有无差错。\r\n在数据链路层,发送端帧检验序列FCS的生成和接收端的CRC检验都是用硬件完成的,处理很迅速,因此并不会延误数据的传输。\r\n\r\n这里需要强调下数据链路层提供的查错控制仅仅是**无比特差错**,无法处理**帧丢失**、**帧重复**、**帧失序**。\r\n\r\n我们知道,过去 OSI 的观点是:必须让数据链路层向上提供可靠传输。因此在CRC检错的基础上,增加了**帧编号**、**确认**和**重传机制**。收到正确的帧就要向发送端发送确认。发送端在一定的期限内若没有收到对方的确认,就认为出现了差错,因而就进行重传,直到收到对方的确认为止。这种方法在历史士兽经起到很好的作用。但现在的通信线路的质量已经大大提高了,因通信链路质量不好引起差错的概率已经大大降低。因此,现在互联网就采取了区别对待的方法:\r\n- 对于通信质量良好的**有线传输链路**,数据链路层协议不使用确认和业传机制,即不要求数据链路层向上提供可靠传输的服务。如果在数据链路层传输数据时出现了差错并且需要进行改正,那么改正差错的任务就由上层协议(例如,运输层的 TCP 协议)来完成。目前数据链路层广泛使用的以太网就不提供可靠性保证。\r\n- 对十通信质量较差的**无线传输链路**,数据链路层协议使用确认和重传机制,数据链路层向上提供可靠传输的服务\r\n## 0.3. 点到点信道的数据链路\r\n在通信线路质量较差的年代,在数据链路层使用可靠传输协议曾经是一种好办法。因此,能实现可靠传输的高级数据链路控制HDLC (High-level Data Link Control)就成为当时比较流行的数据链路层协议。但现在HDLC已很少使用了。对于点对点的链路,简单得多的点对点协议 PPP (Point-to-Point Protocol)则是目前使用得最广泛的数据链路层协议。IETF 在设计互联网体系结构时把其中最复杂的部分放在 TCP 协议中,而网际协议 IP 则相对比较简单,它提供的是不可靠的数据报服务。在这种情况下,数据链路层没有必要提供比 IP 协议更多的功能。因此,对数据链路层的帧,不需要纠错,不需要序号,也不需要流量控制。IETF 把“简单”作为首要的需求。\r\n\r\n点到点信道指的是一条链路上就一个发送端和接收端的信道,通常用在广域网链路。点到点的信道不会发生碰撞,因此比较简单,使用PPP协议控制。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200428153923.png)\r\n\r\n### 0.3.1. 组成部分\r\n\r\nPPP协议主要分成三个部分:\r\n\r\n- 高级数据链路控制协议(HDLC):将IP数据报封装到串行链路,支持同步传输和异步传输。因为计算机内的I/O通信是并行的,而链路传输则是按比特流的串行,因此需要协议的封装转换,一般由网络适配器完成。\r\n- 链路控制协议(LCP):建立、配置和测试链路连接,用来协商一些选项。\r\n- 网络控制协议(NCP):用于支持上层的不同的网络协议。\r\n\r\n\r\n!!! note 同步传输与异步传输\r\n 同步传输(Synchronous Transmission)以数据帧为单位传输数据,可采用字符形式或位组合形式的帧同步信号,在短距离的高速传输中,该时钟信号可由专门的时钟线路传输,由发送端或接收端提供专用于同步的时钟信号。计算机网络采用同步传输方式时,常将时钟同步信号(前同步码)植入数据信号帧中,以实现接收端与发送端的时钟同步。异步传输(Asynchronous Transmission)以字符为单位传输数据,发送端和接收端具有相互独立的时钟(频率相差不能太多),并且两者中任一方都不向对方提供时钟同步信号。\r\n\r\n### 0.3.2. 工作过程\r\n\r\n1. 用户的PC机通过调制解调器呼叫路由器,路由器就能够检测到调制解调器发出的载波信号,在双方建立了物理层的连接之后,PPP协议就进入“链路建立”状态,其目的是建立链路层的LCP连接。\r\n2. LCP开始发送配置请求帧,进行协商一些配置选项,包括链路上的最大帧长、所使用的鉴别协议,以及不适用的PPP帧中的地址和控制字段等。LCP的配置请求帧依旧是一个PPP帧,其协议字段置为LCP对应的代码,而信息字段包含特定的配置请求。链路的另一端可以发送以下几种响应中的一种:(一)配置确认帧:所有选项都接受;(二)配置否认帧:所有选项都理解但不能接受;(三)配置拒绝帧:选项有的无法识别或不能接受,需要协商\r\n3. 协商结束后双方建立了LCP链路,接着就进入鉴别阶段,在这一状态下,只允许传送LCP协议的分组、鉴别协议的分组以及监测链路质量的分组。若鉴别失败,则转到“链路终止”状态,成功则进入“网络层协议”状态。\r\n4. 在“网络层协议”状态,PPP链路两端的网络控制协议NCP根据网络层的不同协议互相交换网络层特定的网络控制分组,总之也许PPP协议两端的网络层可能运行不同的网络层协议,但是都可以使用一个PPP协议进行通信。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200428154652.png)\r\n\r\n### 0.3.3. PPP协议帧格式\r\n- F:PPP协议帧界定符0x7E\r\n- A:在PPP协议中,因为进行通信的只有两方,因此一方发送的数据总是另一方接收,这一点PPP协议不像以太网协议一样,必须使用MAC地址来表明数据帧的发送者和接收者。PPP协议中的Address字段取值**固定为0xff**\r\n- C:字段的值为0x03,无实际意义\r\n- 协议:数据中的协议类型,0x0021-IP数据报;0xC021-LCP数据;0x8021-NCP数据等\r\n- FCS: 长度16bit,用于帧校验。一个设备在收到PPP帧后会进行PPP帧校验,如果发现PPP在传输过程中出错,该帧会被立即丢弃。**PPP协议没有纠错和重传机制**。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200428160127.png)\r\n\r\n为了实现透明传输,还需要对PPP协议进行帧填充,同步传输和异步传输由于机制不同,采用的填充方法也不同。\r\n\r\n- 同步传输。数据以帧为单位,PPP协议采用零比特填充。PPP协议的帧界定符0x7E对应的二进制为01111110,即有6个连续的1。为了避免在数据字段出现帧界定符,因此在每当发送方的数据链路层在数据中遇到连续的5个1,便自动在插入一个比特0,接收方将5个连续1后面的0自动去除。`011011111111111010转换为01101111101111101010->还原为011011111111111010`\r\n- 异步传输:数据传输以字节为单位,使用0x7D作为PPP帧的转义符,并使用字节填充。把信息字段中出现的每一个0x7E字节转变成为2字节序列(0x7D,0x5E),每一个0x7D的字节(即出现了和转义字符一样的比特组合),则把0x7D转变成为2字节序列(0x7D,0x5D)。\r\n\r\nppp协议本身无法直接跨交换机或路由器通信,ppp协议设计上为点对点链路层协议。可以通过PPPoE协议在以太网上传输。\r\n \r\n[PPP协议和PPPoE协议](https://zhuanlan.zhihu.com/p/653263236)\r\n\r\n## 0.4. 广播信道\r\n\r\n广播信道是指一对多通信,一个节点发送的数据能够被广播信道上所有的节点接收到。所有的节点都在同一个广播信道上发送数据,因此需要有专门的控制方法进行协调,避免发生冲突(冲突也叫碰撞)。主要有两种控制方法进行协调,一个是使用信道复用技术,一是使用 CSMA/CD 协议。现在以太网已经成为了事实的局域网标准,采用CSMA/CD协议。\r\n\r\nCSMA/CD(Carrier Sense Multiple Access/Collision Detection,载波多重访问/碰撞检测)工作原理:\r\n\r\n1. 【开始】如果线路空闲,则启动传输,否则跳转到第4步。\r\n2. 【发送】如果检测到冲突,继续发送数据直到达到最小回报时间(min echo receive interval)以确保所有其他转发器和终端检测到冲突,而后跳转到第4步。\r\n3. 【成功传输】向更高层的网络协议报告发送成功,退出传输模式。\r\n4. 【线路繁忙】持续等待直到线路空闲。\r\n5. 【线路空闲】在尚未达到最大尝试次数之前,每隔一段随机时间转到第1步重新尝试。\r\n6. 【超过最大尝试传输次数】向更高层的网络协议报告发送失败,退出传输模式。\r\n\r\n!!! note 以太网为什么不采用信道复用\r\n 主要还是为了降低成本。通常局域网的物理层的传输主要是基于基带传输,CSMA/CD其实可以某种程度上认为是时分复用的技术,而频分复用则需要考虑频谱带宽问题,需要用到调制解调器。如今的以太网其实已经不怎么需要CSMA/CD协议了,交换机的出现取代了集线器。集线器中每一个端口都在同一个冲突域,所有数据包都跑在一条物理链路上,因此需要CSMA/CD协调,而交换机可以隔离冲突域(路由器可以隔离广播域),每一个端口都是独立的冲突域,现在的交换机都采用双绞线作为媒介,发送信道和接收信道独立,工作在全双工模式下,因此不会出现冲突。\r\n\r\n## 0.5. MAC子层和LLC子层\r\n\r\n由于广域网采用点对点通信方式,不存在介质冲突问题,而局域网是共享介质的,因此局域网内的数据链路层需要分为MAC(Media Access Control,媒体访问控制)子层和LLC(Logic Link Control,逻辑链路控制)子层。其中MAC子层的一项重要功能就是仲裁介质的使用权(CSMA/CD)。\r\n\r\n### 0.5.1. MAC子层\r\n\r\nMAC子层是局域网中数据链路层的下层部分,主要功能有:\r\n\r\n- 提供定址及媒体访问的控制方式,使得不同设备或网络上的节点可以在多点的网络上通信,而不会互相冲突\r\n- 数据帧的封装/解封装,帧的寻址和识别,帧的接收与发送,链路的管理,帧的差错控制等。MAC子层的存在屏蔽了不同物理链路种类的差异性。MAC子层作为LLC子层及物理层之间沟通的介质,提供了一种寻址的方法,即MAC地址。MAC地址是唯一的,每个网络适配器的MAC地址都不一样,因此可以在同一子网中发送数据包到特定的目的设备。\r\n\r\n!!! note MAC地址\r\n Mac地址是数据链路层地址,长度为6字节(48)位,用于唯一标识网络适配器。一台主机拥有多少个网络适配器就有多少个Mac地址。Mac地址通常使用6组,每组2位16进制数表示,每组之间冒号隔开。如`82:17:0d:2d:9d:00`\r\n\r\nMAC帧格式\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200428180335.png)\r\n\r\n我们发现MAC帧没有帧开始界定符,那么我们如何区分不同帧呢。其实以太网发送的数据使用了物理层编码违禁法,利用曼彻斯特编码(从高到低跳变表示“1”,从低到高跳变表示“0”),每个码元正中间一定有一次电压转换,当发送方把一个以太网帧发送完毕后,就不在发送其他码元了,因此发送方网络适配器的接口上的电压也就不再变换了。这样接收方就可以知道以太网帧的结束位置了。\r\n\r\n### 0.5.2. LLC子层\r\n\r\n逻辑链路控制子层是局域网中数据链路层的上层部分,用户的数据链路服务通过LLC子层为网络层提供统一的接口。该子层通过在IP包上加了8位的源服务存取点和目的服务存取点(用于标识以太网帧所携带的上层数据类型)来保证在不同网络类型中传输。另外,有一个8或16位的控制字段用于像流控制的辅助功能。\r\n\r\nLLC子层提供了两种无连接和一种面向连接的操作方式:\r\n\r\n- 无回复的无连接方式:它允许发送帧时:给单一的目的地址(单点传输)、给相同网络中的多个目的地址(多点传输)、给网络中的所有地址(广播传输)。多点和广播传输在同一信息需要发送到整个网络的情况下可以减少网络流量。单点传输不能保证接收端收到帧的次序和发送时的次序相同。发送端甚至无法确定接收端是否收到了帧。\r\n- 面向连接的操作方式。给每个帧进行编号,接收端就能保证它们按发送的次序接收,并且没有帧丢失。利用滑动窗口控制协议可以让快的发送端也能流到慢的接收端。\r\n- 有回复的无连接方式。它仅限于点到点通信。\r\n\r\n\r\n\r\n"},{"id":"网络的基本分类","title":"计算机网络(三)—— 网络的基本分类","date":"2020-04-21T00:00:00.000Z","tags":["计算机网络"],"readingTime":11,"slug":"网络的基本分类","description":"本文主要介绍计算机网络的分类以及局域网技术。 0.1. 网络分类 - 地理位置: 1. 个域网(PAN,Personal Area Network)。个域网允许设备围绕一个人进行通信。一个常见的例子是计算机通过无线网络(蓝牙)与其外围设备(显示器、键盘、鼠标。打印机)连接。 2. 局域网(LAN,L...","relativePath":"Tech/Network/概述/网络的基本分类.md","rawContent":"---\r\ntitle: 计算机网络(三)—— 网络的基本分类\r\ndate: 2020-04-21\r\ncategories:\r\n- 计算机网络\r\ntags:\r\n- 计算机网络\r\n\r\n---\r\n\r\n本文主要介绍计算机网络的分类以及局域网技术。\r\n\r\n\r\n\r\n## 0.1. 网络分类\r\n\r\n- 地理位置:\r\n 1. 个域网(PAN,Personal Area Network)。个域网允许设备围绕一个人进行通信。一个常见的例子是计算机通过无线网络(蓝牙)与其外围设备(显示器、键盘、鼠标。打印机)连接。\r\n 2. 局域网(LAN,Local Area Network)。局域网是一种私有网络,一般在一座建筑物内或建筑物附近,如家庭、办公室或工厂。局域网被广泛用来连接个人计算机等电子设备,使它们能够共享资源(打印机)和交换信息。\r\n 3. 城域网(MAN,Metropolitan Area Network)。城域网的范围可覆盖一个城市。最有名的城域网例子是许多城市都有的有线电视网。\r\n 4. 广域网(WAN,Wide Area Network)。广域网的覆盖范围很大,可以跨越很大的地理区域,通常是一个国家,地区甚至大陆。\r\n 5. 互联网(internet)世界上存在着很多网络,它们常常使用不同的硬件和软件,将这些相互之间不同而且常常不兼容的网络连接起来的网络称为互联网。而全球范围内的**因特网(Internet)**常常用首字母大写来表示。\r\n- 网络拓扑结构:\r\n 1. 星型网络结构。星型网络结构是现在最常用的网络拓扑结构,在星型网络结构中各个计算机使用各自的线缆连接到网络中,因此如果一个站点出了问题,不会影响整个网络的运行。\r\n 2. 环型网络结构。这种结构中的传输媒体从一个端用户到另一个端用户,直到将所有的端用户连成环型。数据在环路中沿着一个方向在各个节点间传输,信息从一个节点传到另一个节点。这种结构显而易见消除了端用户通信时对中心系统的依赖性。\r\n 3. 总线型网络结构。在总线型网络结构中所有的站点共享一条数据通道。总线型网络安装简单方便,需要铺设的电缆最短,成本低,某个站点的故障一般不会影响整个网络,但介质的故障会导致网络瘫痪。总线网安全性低,监控比较困难,增加新站点也不如星型网容易。\r\n ![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200425194342.png)\r\n- 传输媒介:\r\n 1. 有线网络。有线网指采用同轴电缆、双绞线、光纤等有线介质连接计算机的网络。\r\n 2. 无线网络。无线网络采用微波、红外线、无线电等电磁波作为传输介质。\r\n\r\n## 0.2. 局域网技术\r\n\r\n局域网技术包括以太网、令牌环网、FDDI网(Fiber Distributed Data Interface)、ATM网(Asynchronous Transfer Mode)。\r\n\r\n### 0.2.1. 以太网\r\n\r\n以太网是当今现有局域网采用的最通用的通信协议标准。IEEE组织的IEEE 802.3标准制定了以太网的技术标准,它规定了包括物理层的连线、电子信号和介质访问层协议的内容。\r\n\r\n#### 0.2.1.1. 分类\r\n\r\n以太网有两类:第一类是经典以太网,第二类是交换式以太网,使用交换机设备连接不同的计算机。经典以太网是以太网的原始形式,运行速度从3~10 Mbps不等;而交换式以太网正是广泛应用的以太网,可运行在100、1000和10000Mbps那样的高速率,分别以快速以太网、千兆以太网和万兆以太网的形式呈现。\r\n\r\n#### 0.2.1.2. 网络结构\r\n\r\n以太网的标准拓扑结构为总线拓扑结构,但目前的快速以太网([100BASE-T](https://baike.baidu.com/item/100BASE-T)、[1000BASE-T](https://baike.baidu.com/item/1000BASE-T)标准)为了减少冲突,将能提高的网络速度和使用效率最大化,使用集线器来进行网络连接和组织。如此一来,以太网的拓扑结构就成了星型;但在逻辑上,以太网仍然使用总线型拓扑和CSMA/CD的总线技术。\r\n\r\nCSMA/CD(Carrier Sense Multiple Access/Collision Detection,载波多重访问/碰撞检测)工作原理:\r\n\r\n1. 【开始】如果线路空闲,则启动传输,否则跳转到第4步。\r\n2. 【发送】如果检测到冲突,继续发送数据直到达到最小回报时间(min echo receive interval)以确保所有其他转发器和终端检测到冲突,而后跳转到第4步。\r\n3. 【成功传输】向更高层的网络协议报告发送成功,退出传输模式。\r\n4. 【线路繁忙】持续等待直到线路空闲。\r\n5. 【线路空闲】在尚未达到最大尝试次数之前,每隔一段随机时间转到第1步重新尝试。\r\n6. 【超过最大尝试传输次数】向更高层的网络协议报告发送失败,退出传输模式。\r\n\r\n因为所有的通信信号都在共享线路上传输,即使信息只是想发给其中的一个终端(destination),却会使用广播的形式,发送给线路上的所有计算机。在正常情况下,网络接口卡会滤掉不是发送给自己的信息,接收到目标地址是自己的信息时才会向CPU发出中断请求,除非网卡处于混杂模式(Promiscuous mode)。这种“一个说,大家听”的特质是共享介质以太网在安全上的弱点,因为以太网上的一个节点可以选择是否监听线路上传输的所有信息。共享电缆也意味着共享带宽,所以在某些情况下以太网的速度可能会非常慢,比如电源故障之后,当所有的网络终端都重新启动时。\r\n\r\n#### 0.2.1.3. 工作模式\r\n\r\n以太网卡可以工作在两种模式下:半双工和全双工\r\n\r\n- 半双工:半双工传输模式实现以太网载波监听多路访问冲突检测。传统的共享LAN是在半双工下工作的,在同一时间只能传输单一方向的数据。当两个方向的数据同时传输时,就会产生冲突,这会降低以太网的效率。\r\n- 全双工:全双工传输是采用点对点连接,这种安排没有冲突,因为它们使用双绞线中两个独立的线路,这等于没有安装新的介质就提高了带宽(单行道变双行道)。在双全工模式下,冲突检测电路不可用,因此每个双全工连接只用一个端口,用于点对点连接。标准以太网的传输效率可达到50%~60%的带宽,双全工在两个方向上都提供100%的效率。\r\n\r\n### 0.2.2. 令牌环网\r\n\r\n令牌环网是IBM公司于70年代发展的,现在这种网络比较少见。在老式的令牌环网中,数据传输速度为4Mbps或16Mbps,新型的快速令牌环网速度可达100Mbps。令牌环网的传输方法在物理上采用了星形拓扑结构,但逻辑上仍是环形拓扑结构。其通信传输介质可以是无屏蔽双绞线、屏蔽双绞线和光纤等。 结点间采用多站访问部件(Multistation Access Unit,MAU)连接在一起。MAU是一种专业化集线器,它是用来围绕工作站计算机的环路进行传输。由于数据包看起来像在环中传输,所以在工作站和MAU中没有终结器。在这种网络中,有一种专门的帧称为“令牌”,在环路上持续地传输来确定一个结点何时可以发送包。令牌为24位长,有3个8位的域,分别是首定界符(Start Delimiter,SD)、访问控制(Access Control,AC)和终定界符(End Delimiter,ED)。首定界符是一种与众不同的信号模式,作为一种非数据信号表现出来,用途是防止它被解释成其它东西。这种独特的8位组合只能被识别为帧首标识符(SOF)。由于目前以太网技术发展迅速,令牌网存在固有缺点,令牌在整个计算机局域网已不多见,原来提供令牌网设备的厂商多数也退出了市场,所以在目前局域网市场中令牌网可以说是“明日黄花”了。\r\n\r\n \r\n\r\n"},{"id":"性能指标","title":"计算机网络(二)—— 性能指标","date":"2020-04-20T00:00:00.000Z","tags":["计算机网络"],"readingTime":9,"slug":"性能指标","description":"计算机网络的常见的性能指标有速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率。 1. 速率(bit/s或byte/s) 速率是物理层概念,指的是信道上每秒钟传输的0/1比特数量。比如下载文件中显示的7.8MB/s指的是每秒中可以传输7.8M字节的信息。 2. 带宽(bit/s) 带宽是逻辑概念...","relativePath":"Tech/Network/概述/性能指标.md","rawContent":"---\r\ntitle: 计算机网络(二)—— 性能指标\r\ndate: 2020-04-20\r\ncategories:\r\n- 计算机网络\r\ntags:\r\n- 计算机网络\r\n---\r\n\r\n计算机网络的常见的性能指标有速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率。\r\n\r\n\r\n\r\n# 1. 速率(bit/s或byte/s)\r\n\r\n速率是物理层概念,指的是信道上每秒钟传输的0/1比特数量。比如下载文件中显示的7.8MB/s指的是每秒中可以传输7.8M字节的信息。\r\n\r\n# 2. 带宽(bit/s)\r\n\r\n带宽是逻辑概念,表示网络信道传输数据的能力,即最高速率。不同的信道占据着不同的频谱,信道在最大频带下能够达到的最大速率称之为带宽。我们常常会在运营商办理入网服务,常见的百兆带宽指的是100Mbps,即(12800byte/s)。通信网络包括核心网、传输网、接入网等,运营商一般在位于接入网的入口处进行限速,一旦用户流量跨过接入网,就不再限速。\r\n\r\n# 3. 信道容量(bit/s)\r\n\r\n香农定理给出了信道信息传送速率的上限(比特每秒)和信道信噪比及带宽的关系。在有随机热噪声的信道上传输数据信号时,信道容量$C_{max}$与信道带宽W,信噪比$S/N$关系为:\r\n\r\n$\r\nC_{max}=W*\\log_{2}(1+S/N)\r\n$\r\n简单来看,就是信道带宽越大,信噪比越大(信号越强,噪音越弱),信道容量越大。香农定理指导我们,只要实际传输速率$R 此处带宽是与计网中的带宽概念不同,通信领域“带宽”指的信号所占据的频带宽度(以赫兹Hz为单位)。例如,在普通电话线上提高访问Internet的ADSL使用了大约1MHz的带宽,对于1-2千米的短距离来说,40分贝的信噪比已经算是比较好的状况了。正是由于电话线具有这样的特性,因此我们永远不可能期望在电话线上获得高于13Mbps的速率。而光纤的带宽大约有3000GHz,是传统电话线的200000倍。\r\n\r\n# 4. 吞吐量(Throughput)\r\n\r\n吞吐量表示在单位时间内通过某个网络或接口的数据量,包括全部上传和下载的流量。通常我们更倾向于用“吞吐量”一词来表示一个系统的测试性能。因为受各种低效率因素的影响,一段带宽为10Mbps的链路连接的一对节点可能只达到2Mbps的吞吐量。\r\n\r\n# 5. 时延(delay/latency)\r\n\r\n时延是指数据(一个数据包或bit)从网络的一端传送到另一端所需要的时间。总时延=发送时延+传播时延+排队时延+处理时延。\r\n\r\n- 发送时延(transmission delay)是主机或路由器发送数据帧所需时间,也就是从发送数据帧的第一个比特开始,到该帧最后一个比特发送完毕所需要的时间。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424213210.png)\r\n\r\n- 传播时延(propagation delay)是电磁波在信道中传播一定的距离需要花费的时间。主机或路由器发送完数据到最后一比特到达主机或路由器的所需时间。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424213227.png)\r\n\r\n- 排队时延是指分组在经过网络传输时,要经过许多的路由器。但分组在进入路由器后要先在输入队列中排队等待处理。\r\n- 处理时延是指路由器或主机在收到数据包时,要花费一定时间进行处理,例如分析数据包的首部、进行首部差错检验,查找路由表给数据包选定转发出口,这就产生了处理时延。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424213336.png)\r\n\r\n# 6. 时延带宽积\r\n\r\n时延带宽积=传播时延×带宽。表示在特定时间里,网络上的最大数据量。\r\n\r\n> 假设两台电脑百兆网口接在一起用Chariot跑TCP流量,电脑的窗口大小为64KB(窗口不自动扩大)。它们之间线路速度为100Mb/s ,RTT稳定为1ms,那么带宽时延乘积为100Kb (12.5KB,tcp已发送未确认的数据), 小于窗口大小,那么跑出来的速度是接近线速(100Mb/s)的。如果RTT稳定为10ms,那么理论速度为64*8/10=51.2Mb/s。\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424213612.png)\r\n\r\n# 7. 往返时间(Round-Trip Time,RTT) \r\n往返时间表示从发送方发送数据开始,到发送方接收到来自接收方的确认,总共经历的时间。\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images20250321200519498.png)\r\n# 8. 利用率\r\n\r\n利用率是指的网络有百分之几的时间是被利用的(有数据通过),没有数据通过的网络利用率为零。U是网络利用率,D表示网络当前时延,$D_0$表示网络空闲时的时延。$D=D_0/(1-U)$\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200424214055.png)\r\n\r\n# 9. 带宽和网络吞吐量\r\n\r\n带宽表示可以通过网络传输的数据总量。总带宽是指可以通过网络传输的理论最大数据量。测量单位是兆字节每秒(MBps)。可以将带宽视为网络的理论最大吞吐量。 \r\n\r\n带宽是指可以传输的数据量,而吞吐量是指根据实际网络限制,在任何给定时刻传输的实际数据量。高带宽并不能保证速度或良好的网络性能,但更高的带宽会导致更高的吞吐量。\r\n# 10. web服务性能指标\r\n\r\n# 11. RT(Response time,响应时间)\r\n\r\n响应时间是指系统对请求作出响应的时间。对于我们常见的手游来说,比如王者荣耀100ms的响应时间应该是不错的,400ms则到了无法忍受的程度。\r\n\r\n## 11.1. TPS(Transaction Per Second,每秒事务数)\r\n\r\n在性能测试工具中,TPS也被称之为吞吐量。吞吐量直接体现系统性能的承载能力,是指单位时间内处理的客户请求的数量。其计量单位可以根据需求不同而不同,比如请求数/秒,页面数/秒,业务数/小时\r\n\r\n## 11.2. QPS(Query Per Second,每秒查询数)\r\n\r\n每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。 (看来是类似于TPS,只是应用于特定场景的吞吐量)\r\n\r\n## 11.3. 并发用户数\r\n\r\n并发用户数对于一个系统来说具有统计意义,指系统可以同时承载的正常使用系统功能的用户的数量。实际上,并发用户数是一个非常不准确的指标,因为用户不同的使用模式会导致不同用户在单位时间发出不同数量的请求。\r\n\r\n\r\n\r\n"},{"id":"分层模型","title":"计算机网络(一)—— 分层模型","date":"2020-04-19T00:00:00.000Z","tags":["计算机网络"],"readingTime":5,"slug":"分层模型","description":"计算机网络按照使用范围可以划分为个域网、局域网、城域网、广域网和互联网,其组成复杂,存在着大量诸如集线器、交换机、路由器、笔记本、手机等硬件。为了降低网络设计的复杂性,现阶段大部分网络都会形成一个层次栈结构,每一层都建立在其下一层的基础之上,向上一层提供特定服务。这种分层结构的概念其实和计算机领域的...","relativePath":"Tech/Network/概述/分层模型.md","rawContent":"---\r\ntitle: 计算机网络(一)—— 分层模型\r\ndate: 2020-04-19\r\ncategories:\r\n- 计算机网络\r\ntags:\r\n- 计算机网络\r\n\r\n---\r\n\r\n计算机网络按照使用范围可以划分为个域网、局域网、城域网、广域网和互联网,其组成复杂,存在着大量诸如集线器、交换机、路由器、笔记本、手机等硬件。为了降低网络设计的复杂性,现阶段大部分网络都会形成一个层次栈结构,每一层都建立在其下一层的基础之上,向上一层提供特定服务。这种分层结构的概念其实和计算机领域的面向对象思想类似,每一层对内部状态和算法细节隐藏,对外暴露使用方式,实现层与层之间的解耦。\r\n\r\n\r\n\r\n## 0.1. 引言\r\n\r\n计算机网络的体系有两种常见的体系结构:OSI七层模型以及TCP/IP四层模型。OSI(开放系统互连,Open System Interconnection)模型本身没有定义每一层服务和所有的协议,它只是指明了每一层应该做一些什么事情,而ISO(国际标准化组织,International Organization for Standardization)则为所有层都制定了相应的标准,但这些标准并不是属于参考模型本身,每个协议都是作为单独的国际标准发布的。TCP/IP参考模型以其最主要的两个协议命名,其设计的主要目标是为了保证在源机器和目标机器之间的一些及其或者传输线路突然不能工作,但源和目标机器还在运作的情况下,他们之间的连接维持不变。总的来说,OSI侧重抽象,强调功能是什么,而TCP/IP侧重实现,设计协议完成功能。\r\n\r\n## 0.2. OSI七层模型\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200423160451.png)\r\n\r\n- 物理层。物理层关注一条通信信道上传输原始0/1比特,主要涉及机械、电子和时序接口,典型问题包括用什么电子信号来表示1和0,一个比特持续多长时间,传输是否可以在两个方向上同时进行等(全双工/半双工/单工)。\r\n- 数据链路层。数据链路层主要任务是将一个原始的传输设施转变成一条没有漏检传输错误的线路。数据链路层通过**数据帧**的方式传输数据。\r\n- 网络层。网络层的主要功能是控制子网的运行,关键问题是如何将**数据包**从源端路由到接收方。\r\n- 传输层。传输层是真正的端到端的层。传输层不关注链路中间的节点,而在传输层之下的隔层,每一个协议涉及一台机器和与他直接相连的邻居。\r\n- 会话层。允许不同机器上的用户建立会话,包括对话控制、令牌管理以及同步功能\r\n- 表示层。表示层关注所传递信息的语法和语义,让具有不同内部数据表示法的计算机可以相互通信,约定同一种编码方式\r\n- 应用层。应用层直接面向用户,提供各种常用功能。\r\n\r\n## 0.3. TCP/IP四层模型\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200423164013.png)\r\n\r\n## 0.4. 封装与解封装\r\n\r\n![](https://shinerio.oss-cn-beijing.aliyuncs.com/blog_images/uncategory/20200423170304.png)\r\n\r\n实际使用中,我们常用5层混合模型。数据在网络中的传输过程需要经过各种不同层级设备,链路中每个设备节点需要通过封装与解封装来完成数据的传输和路由。\r\n\r\n## 0.5. 引用\r\n\r\n1. [一篇文章带你熟悉 TCP/IP 协议(网络协议篇二)](https://www.jianshu.com/p/9f3e879a4c9c)\r\n2. 计算机网络. Andrew S. Tanenbaum, David J. Wetherall(严伟等译)."},{"id":"加密算法","title":"加密算法","date":"2020-04-07T00:00:00.000Z","tags":["网络安全","加密"],"readingTime":15,"slug":"加密算法","description":"本文主要介绍密码学中常见的两种加密算法—对称加密和非对称加密。 1. 散列(摘要)算法 在学习加密算法之前,我们先来了解一下散列算法(散列不是加密)。散列算法是通过一定方式对原文进行计算,产生一个哈希值,不管原始数据是什么样的,得到的哈希值都是固定长度的,其作用只是为了验证数据的完整性和唯一性,无法...","relativePath":"Tech/security/加密算法.md","rawContent":"---\r\ntitle: 加密算法\r\ndate: 2020-04-07\r\ncategories:\r\n- 网络安全\r\ntags:\r\n- 网络安全\r\n- 加密\r\n---\r\n\r\n本文主要介绍密码学中常见的两种加密算法—对称加密和非对称加密。\r\n# 1. 散列(摘要)算法\r\n在学习加密算法之前,我们先来了解一下散列算法(散列不是加密)。散列算法是通过一定方式对原文进行计算,产生一个哈希值,不管原始数据是什么样的,得到的哈希值都是固定长度的,其作用只是为了验证数据的完整性和唯一性,无法通过摘要解密得到原始数据。而对于加密算法来说,我们可以通过“秘钥”或者“解密算法”将加密后的结果还原成原文。\r\n> [!note]\r\n> 常见的散列算法有:SM3、MD5、SHA-1\r\n## 1.1. Example1\r\n我们可以设计一个简单的对整数进行散列的算法\r\n ```python\r\n def integer_hash(x):\r\n return x % 16\r\n \r\n \r\n if __name__ == \"__main__\":\r\n print(integer_hash(16), integer_hash(17), integer_hash(32))\r\n \r\n >>> 0 1 0\r\n ```\r\n我们采用对16取余的方案,可以看到对16和17进行hash后得到的摘要是不一样的。在实际传输过程中,我们可以通过同时传输原文和散列后的摘要,接收方通过对原文进行相同散列,比较得到的摘要和接收的摘要是否相同来验证文本的正确性。但是通过hash后的结果“0”或“1”并不能得到原文,而且不同的原文hash后的结果也可能相同,如16和32的hash结果都是0。当然,我们这里的hash算法过于简单,一个优秀的hash算法应该保证散列的随机性,尽量减少冲突。\r\n## 1.2. Example2\r\n ```python\r\n import hashlib\r\n \r\n \r\n str = \"This text is hashed via md5\"\r\n \r\n hl = hashlib.md5()\r\n hl.update(str.encode(encoding='utf-8'))\r\n \r\n print(\"MD5散列前:\" + str)\r\n print(\"MD5散列后:\" + hl.hexdigest())\r\n >>> MD5散列前:This text is hashed via md5\r\n >>> MD5散列后:5d3a325f6a7fd4c8e4d4dad3879cfcdd\r\n ```\r\n我们常常可以看到网上有工具可以对MD5算法加密后的结果进行还原,那是由于MD5算法设计足够优秀,可以保证无冲突,因此我们可以保留很多“摘要-原文”字典,通过hash值来暴力求取原文。由于是通过字典暴力求解,所以一般缓存的都是常用的、较短的字符串,本例中的文本较长且不常见,因此将本例中的摘要通过工具进行解密往往不能成功。如果加“hello”的MD5结果加以解密,则很快能得到结果。很多网站常常用MD5来保存散列后的密码来避免明文存储,这时候可以通过加盐的方式进一步提高安全性。所谓加盐就是通过特定的方法将某一个固定的字符串或随机的字符串(需要保存)插入到原文中去,对原文进行改造。\r\n# 2. 对称加密\r\n对称加密指的是加、解密使用的同是一串秘钥,所以被称作对称加密。\r\n> [!note]\r\n> 常见的对称加密算法有**DES、AES**等\r\n\r\n对称算法又可分为两类:\r\n- **序列算法或序列密码**:一次只对明文中的单个位(有时对字节)运算的算法。\r\n- **分组算法或分组密码**:对明文的一组位进行运算,这些位组称为分组\r\n\r\n现代计算机密码算法的典型分组长度为64位――这个长度既考虑到分析破译密码的难度,又考虑到使用的方便性。后来,随着破译能力的发展,分组长度又提高到128位或更长。\r\n- 优点:加解密效率高,加密速度快。\r\n- 缺点:秘钥保管困难,网络传输秘钥不安全\r\n\r\n```python\r\nimport binascii\r\nfrom pyDes import des, CBC, PAD_PKCS5\r\n\r\n\r\ndef des_encrypt(str, secret_key):\r\n\r\n k = des(secret_key, CBC, secret_key, pad=None, padmode=PAD_PKCS5)\r\n en = k.encrypt(str, padmode=PAD_PKCS5)\r\n return binascii.b2a_hex(en)\r\n\r\n\r\ndef des_descrypt(str, secret_key):\r\n\r\n k = des(secret_key, CBC, secret_key, pad=None, padmode=PAD_PKCS5)\r\n de = k.decrypt(binascii.a2b_hex(str), padmode=PAD_PKCS5)\r\n return de\r\n\r\n\r\nif __name__ == \"__main__\":\r\n\r\n str_en = des_encrypt('hello world', \"20200407\")\r\n print(str_en.decode('utf-8'))\r\n str_de = des_descrypt(str_en, \"20200407\")\r\n print(\"正确的key解密结果:\" + str_de.decode('utf-8'))\r\n str_de = des_descrypt(str_en, \"20210407\")\r\n print(\"错误的key解密结果:\" + str_de.decode('utf-8'))\r\n \r\n \r\n>>> 886a9aca4d78c90016a81d3a296544ca\r\n>>> 正确的key解密结果:hello world\r\n>>> 错误的key解密结果:helmo world\r\n```\r\n\r\n# 3. 非对称加密\r\n非对称加密指的是加、解密使用不同的秘钥,一把作为公钥、另一把作为私钥。公钥加密的信息,只有私钥才能解密,且私钥加密的信息,只有公钥才能解密。\r\n- 优点:比较安全\r\n- 缺点:效率较低\r\n\r\n> [!note]\r\n> 常见非对称加密算法有:RSA、ECC(椭圆曲线加密算法)\r\n## 3.1. RSA与ECC\r\n- **ECC(椭圆曲线密码学)**:是一种基于椭圆曲线数学理论实现的非对称加密算法。它通过椭圆曲线上的点运算来构建公钥和私钥,其安全性基于椭圆曲线上的离散对数难题,即给定椭圆曲线上的一个点和一个倍数,很难计算出这个倍数。\r\n- **RSA**:同样是一种非对称加密算法,由 Ron Rivest、Adi Shamir 和 Leonard Adleman 在 1977 年提出。它的安全性基于大整数分解难题,也就是将一个大的合数分解为两个质数的乘积是非常困难的。\r\n\r\nECC优势\r\n- **性能优势**:在相同安全强度下,ECC 密钥长度比 RSA 短很多,例如 256 位的 ECC 密钥与 3072 位的 RSA 密钥具有相当的安全性。这使得 ECC 在加密和解密操作时速度更快,尤其是在移动设备和物联网等资源受限的环境中,ECC 能更好地适应,减少计算资源和能源的消耗。\r\n- **安全特性**:ECC 基于椭圆曲线上的离散对数难题,数学结构复杂,在面对量子计算机攻击时,被认为可能比 RSA 更具抵抗力。随着量子计算技术的发展,这一特性使 ECC 在未来的信息安全领域具有重要的战略意义。\r\nRSA优势\r\n- **历史悠久**:RSA 是最早提出的非对称加密算法之一,在信息安全领域应用时间长,许多现有系统和标准都是基于 RSA 构建的,具有广泛的兼容性。\r\n- **理解和实现相对容易**:RSA 的数学原理相对直观,基于大整数的乘法和分解,在一些对安全性要求不是极高、资源较为充足且追求简单实现的场景中,仍然被广泛使用。\r\n## 3.2. 密钥交换\r\n- **ECDH(椭圆曲线 Diffie - Hellman)**:基于ECC的密钥交换协议,用于在不安全的通信信道上,让双方安全地交换共享密钥。双方通过各自的私钥和对方的公钥,在椭圆曲线上进行计算,最终得到相同的共享密钥。\r\n- RSA也可以用于密钥交换。\r\n### 3.2.1. ECDH协商简要过程\r\n1. **参数选择**:通信双方(如 A 和 B)首先需要选择一个共同的椭圆曲线参数,包括椭圆曲线方程、基点G以及曲线的阶n等。这些参数是公开的,可通过标准组织或预定义的方式获取。\r\n2. **密钥生成**\r\n - 参与者A随机选择一个整数a作为自己的私钥,计算$A=aG$作为自己的公钥,其中G是椭圆曲线的基点。\r\n - 参与者B同样随机选择一个整数b作为自己的私钥,计算$B=bG$作为自己的公钥。\r\n3. **公钥交换**:A和B通过不安全的信道交换各自的公钥A和B。\r\n4. **共享密钥计算**\r\n - 参与者 A 使用自己的私钥a和收到的B的公钥 B,计算共享密钥 K=aB。\r\n - 参与者 B 使用自己的私钥b和收到的A的公钥 A,计算共享密钥 K=bA。 \r\n 由于 aB=a(bG)=b(aG)=bA,所以双方计算得到的共享密钥是相同的。\r\n> [!note]\r\n> 1. 密钥协商过程中,私钥是本地随机生成的,私钥没有参与过网络传输,没有被窃取的可能。\r\n> 2. 私钥是每次通信临时生成(通过随机数a、b)的,用完即销毁,也不用担心后面私钥会泄露的问题!\r\n> 3. 加密使用的对称秘钥也是各自本地计算得到的,没有在网络中传输\r\n### 3.2.2. 随机生成非对称密钥的必要性\r\n如果仅使用服务端证书中的公钥来加密对称密钥(如TLS早期的**RSA 密钥交换模式**),那么这种方式无法提供**前向安全性**(Forward Secrecy,FS)。\r\n- 如果未来某个攻击者获取了服务器的私钥(比如通过黑客攻击、量子计算等方式破解),则所有过去的通信数据都可以被解密。\r\n- 但如果使用 **ECDH**(或者传统的 DH),每次通信的密钥都是临时协商的,服务器的私钥即使泄露,也无法解密过去的通信数据。\r\n> [!note]\r\n现代的HTTPS协议(如 TLS 1.2 及 TLS 1.3)广泛采用 **ECDHE(Ephemeral ECDH)** 来保证前向安全性,使得每次通信都有独立的临时密钥,即使私钥泄露也不会影响之前的会话。在 TLS 1.3 中,RSA 密钥交换已经被移除,所有密钥交换必须使用 **ECDHE 或 DHE**,原因正是 **前向安全性** 和 **更高效的加密性能**。\r\n\r\n> [!note]\r\n> 可以使用临时会话级RSA密钥来实现前向安全性,但它有以下缺点:\r\n> 1. **计算开销过大**(临时 RSA 密钥的生成和解密计算成本高,影响服务器性能)\r\n> 2. **额外的公钥传输成本**(临时 RSA 公钥较长,导致握手数据增加)。\r\n## 3.3. 签名\r\n- **ECDSA(椭圆曲线数字签名算法)**:基于ECC的数字签名算法,用于对消息进行签名和验证签名的有效性。签名者使用自己的私钥对消息进行签名,验证者使用签名者的公钥来验证签名是否有效。\r\n- RSA也可以用于签名\r\n## 3.4. 应用示例\r\n### 3.4.1. 应用场景一(加密) \r\n(1)乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。\r\n(2)甲方获取乙方的公钥,然后用它对信息加密。\r\n(3)乙方得到加密后的信息,用私钥解密。\r\n### 3.4.2. 应用场景二(验证签名)\r\n(1)甲方生成两把秘钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。\r\n(2)甲方用散列算法生成文件摘要,然后用私钥对摘要进行加密生成签名。\r\n(3)乙方拿到文件文件后,用相同的散列算法生成摘要,用公钥对签名进行解密,比较两份摘要是否相同\r\n> [!tip]\r\n> 数字签名可以保证甲方发给乙方的文件是完整的、没有被篡改过的。由于只有甲方拥有私钥,篡改者无法通过私钥进行加密,也就无法篡改签名。因此接收方如果通过公钥对签名进行解密得到的摘要与在接收方对原文进行散列得到的摘要不相同的话,就可以断定原文已经被修改过了。\r\n\r\n示例:\r\n```python\r\nimport rsa\r\nimport hashlib\r\n\r\n\r\ndef create_keys():\r\n\r\n \"\"\"\r\n 生成秘钥对\r\n \"\"\"\r\n\r\n (pubkey, prikey) = rsa.newkeys(1024)\r\n pub = pubkey.save_pkcs1()\r\n pri = prikey.save_pkcs1()\r\n\r\n with open('rsa.pub', 'wb+') as f:\r\n f.write(pub)\r\n\r\n with open('rsa.pri', 'wb+') as f:\r\n f.write(pri)\r\n\r\n\r\ndef encrypt(original_text):\r\n \"\"\"\r\n 公钥加密\r\n \"\"\"\r\n with open('rsa.pub', 'rb') as f:\r\n pub = f.read()\r\n pubkey = rsa.PublicKey.load_pkcs1(pub)\r\n encrypted_text = rsa.encrypt(original_text.encode('utf-8'), pubkey)\r\n return encrypted_text\r\n\r\n\r\ndef decrypt(encrypted_text):\r\n \"\"\"\r\n 私钥解密\r\n \"\"\"\r\n with open('rsa.pri', 'rb') as f:\r\n pri = f.read()\r\n prikey = rsa.PrivateKey.load_pkcs1(pri)\r\n original_text = rsa.decrypt(encrypted_text, prikey).decode(\"utf-8\")\r\n return original_text\r\n\r\n\r\ndef sign(message):\r\n \"\"\"\r\n 私钥签名\r\n \"\"\"\r\n with open('rsa.pri', 'rb') as f:\r\n pri = f.read()\r\n prikey = rsa.PrivateKey.load_pkcs1(pri)\r\n # 使用MD5所有散列算法生成摘要\r\n signed_message = rsa.sign(message.encode('utf-8'), prikey, hash_method='MD5')\r\n return signed_message\r\n\r\n\r\ndef verify(message, signed_message):\r\n \"\"\"\r\n 公钥验证\r\n \"\"\"\r\n with open('rsa.pub', 'rb') as f:\r\n pub = f.read()\r\n pubkey = rsa.PublicKey.load_pkcs1(pub)\r\n return rsa.verify(message.encode('utf-8'), signed_message, pubkey)\r\n\r\n\r\nif __name__ == \"__main__\":\r\n text = \"This is encrypted by rsa\"\r\n create_keys()\r\n text = encrypt(text)\r\n print(\"加密结果:\", text)\r\n text = decrypt(text)\r\n print(\"解密结果:\", text)\r\n signed_message = sign(text)\r\n print(\"签名结果:\", signed_message)\r\n print(\"验签结果:\", verify(text, signed_message))\r\n\r\n>>> MD5加密前:This text is hashed via md5\r\n>>> MD5加密后:5d3a325f6a7fd4c8e4d4dad3879cfcdd\r\n>>> 签名结果: b'\\x10^\\xa53\\x8a\\xda\\xf0\"e}\\x7f\\x8dtg\\x0f\\x96\\x08\\xd2\\xa2L\\x16#c\\xc6\\xad*C\\xa6\\x84\\x11A\\xe9\\t\\xf9\\\\\\xce\\x03\\x99\\xf1\\xb4\\xfa\\xdf\\x83\\xa4[\\x1d\\xbff\\x9eO\\x08\\x92\\xf4\\x05E4\"&v\\xcfTD\\xbbq\\x87\\x9f\\xdb\\xc0u\\x9b(\\x13\\\\\\x0b>> 验签结果:MD5\r\n```\r\n\r\n# 4. 对称加密与非对称加密对比\r\n- 对称加密多用户通信秘钥管理困难,N个人两两之间需要保管一个秘钥,共$C_N^2$个秘钥对。\r\n- 非对称加密,N个用户,共有N个私钥和N个公钥。\r\n- 在实际运用中可以将两种加密方式混合使用,将对称加密的密钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用私钥进行解密得到对称加密的密钥,然后双方可以使用对称加密来进行沟通。\r\n# 5. 参考文献\r\n1. [摘要与加密的区别](https://juejin.im/post/5a7d11be5188257a5e5738c3)\r\n2. [DES加密算法原理](https://www.jianshu.com/p/c44a8a1b7c38)\r\n3. [非对称加密算法](https://www.liaoxuefeng.com/wiki/1252599548343744/1304227873816610)"}]

Github Oauth

▐▛███▜▌ Claude Code v2.1.34 ▝▜█████▛▘ Opus 4.6 · Claude API ▘▘ ▝▝ C:\workspace\code\shinerio.github.io > 1. 文章详情页,点击划词后,点击使用github登录,页面直接404,需要修复 2. 底...

BGP Speaker

在传统的网络设备中,我们通常直觉地认为“一台设备 = 一个 BGP 进程 = 一个 Speaker”。但在现代网络设计和高级设备架构中:一台物理路由器可以拥有多个 BGP Speaker。 为了理解这一点,我们需要区分“物理实体”和“逻辑协议实例”。以下是几种实现“多 Speaker”的典型场景: ...

BGP路由器分类

在BGP(边界网关协议)中,路由器的分类通常根据其在自治系统(AS)中的位置以及建立邻居关系的方式来划分。 1. 按协议划分 1.1. EBGP 路由器 (External BGP) 当两台运行 BGP 的路由器处于不同的自治系统(AS)时,它们之间建立的邻居关系称为 EBGP。 - 位置: 通常位...

Awesome Tools

1. 全平台 - freefilesync,文件夹同步工具 - ChatGPT流媒体解锁检测脚本: - https://gitlab.com/fscarmen/warp解锁应用端chatgpt 2. macos 2.1. 媒体工具 - snipaste截图软件 - kap屏幕gif录制工具,对于博客...

Claude Code

1. claude code配置 1.1. ide集成 在vscode中安装claude code插件,然后在claude code命令行界面使用即可连接到vscode。claude code就可以和vscode进行交付,感知你在vscode中选中的代码、文件,claude code的修改也会在vs...

Subagents

1. 子代理是专门处理特定类型任务的 AI 助手,当Claude遇到与子代理描述相匹配的任务时,它会将任务委派给该子代理,由其独立工作并返回结果。 - 每个子代理都在自己的上下文窗口中运行 - 拥有自定义的系统提示词(System Prompt) - 特定的工具访问权限和独立的权限设置。 优势: -...

Cloudflare免费Worker

cloudflare一段时间之前推出的一项免费服务, 允许在CDN服务器上运行js脚本或wasm 截止到这篇文章写完的时候,这仍是一项长期免费服务,免费套餐为每天 100000 个请求, 大概是100个人 每人请求100次, 或0.01个人 每人请求10000000次

Quantumult X

1. 参考配置 https://raw.githubusercontent.com/limbopro/Profiles4limbo/main/full.conf 2. 规则仓库 https://github.com/blackmatrix7/iosrulescript/tree/master 3. ...

Cloud Wan竞品分析

1. 腾讯 - CCN路由同步,默认会同步所有路由表 - - VPC多条路由冲突的时候,可以支持启用、停用 1.1. 跨地域流量管理 - 单向的流量调度规则限速带宽总和不得超过带宽上限。 - 带宽上限提高后,默认规则的限速带宽不会自动调整,需手动调整 2. 阿里 - CEN路由同步,默认只会同步默认...

IBGP与EBGP区别

1. 使用场景 eBGP主要用于: - 不同运营商/组织之间交换路由,例如中国电信与中国联通的互联互通 - 企业多归属(multihoming)接入多个 ISP - IXP(互联网交换点)中各参与方之间的路由交换 iBGP 主要用于: - 在一个大型 AS 内部传递从 eBGP 学到的外部路由信息,...

BGP协议优先级

1. 核心逻辑 1.1. “大”即是好的(高优先级) Weight 和 Local Preference:这两个属性是管理员手动干预的首选。数值设置得越大,代表你越“偏好”这条路径。 1.2. “短/小”即是好的(低开销) - AS-Path 长度:这体现了 BGP 的路径矢量特性,跳数越少代表路径...

Openspec

A "change" in 是一个“承载着围绕一项工作所进行的所有思考和规划的“集合。文件夹位于,包含proposal, specs, design, tasks。 工作流程 目录结构 - - 这是最重要的目录,存储了系统当前是如何运行的完整描述。 - 按domain组织:为了防止单个文档过大,它按...

Claudemd

| 类型 | 位置 | 范围 | 版本控制 | 典型用途 | | ------ | ----------------------- | ---- | ----- | ------ | | 全局 | | 所有项目 | 不共享 | 个人编码偏好 | | 项目 | | 当前项目 | 共享给团队 | 团队规...

SDD(Spec Driven Develop)

Kiro的spec流程被设计为三个步骤:需求 (requirements.md) → 设计 (design.md) → 任务 (tasks.md)。每个工作流步骤都由一个Markdown文档表示,Kiro会引导你达成这三个步骤。 1. 需求文档 它被构建为一个需求列表 1. 每个需求代表一个“用户故...

Run Code

runcode是一个来自veadk库的内置工具,它提供了一个安全的代码执行沙箱功能。这个工具允许AI Agent运行用户请求的代码片段(主要是 Python3),并返回执行结果。该工具通过字节跳动云服务的API在远程安全环境中执行代码,并具有会话管理和身份验证功能。 1. 工作原理 1.1. 工具注...

Demo

1. summary 2. 3. 对话前,无任何相关记忆 通过对话告知喜欢滑雪 再次查询记忆库 4. 5. 部署MCP服务的时候可以选择API KEY自动生成MCP的入站身份鉴权 agentkit一直无法拉起mcp工具集,这里使用claude code本地测试 6. 环境变量参考 7. 相关链接 -...

Awscli

install 配置账号 1. ref https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

Invoke

调用需要具备以下权限

Introduce

Amazon Bedrock AgentCore 同时具备框架无关性和模型无关性,让你能够灵活、安全且大规模地部署和运行高级 AI 智能体。无论你是使用 Strands Agents、CrewAI、LangGraph、LlamaIndex 或任何其他框架构建智能体,也无论你在哪个大语言模型(LLM)...

Gateway

1. v2n agent runtime需要的short-term memory需要通过v2n连接到用户vpc中的postgresql(内存中易失)

AgentCore

Amazon Bedrock AgentCore是用于构建、部署、高效运行Agent的最先进的智能化平台,其服务包括Runtime, Gateway, Memory, Identity, Observability, Browser, Code Interpreter, Evaluation, po...

Oauth2.0

OAuth 2.0是一个关于授权(Authorization)的开放标准。它允许用户让第三方应用访问该用户在特定服务上存储的私有资源(如照片、联系人等),而无需将用户名和密码提供给第三方应用。最常见的例子就是:你使用“微信登录”或“GitHub登录”来注册一个新的网站,而不需要在该网站重新设置密码。...

记忆

1. 短期记忆 LLM的核心架构在推理时,每一轮都是独立的计算过程。因此如果不把所有对话历史都带上的话,LLM就只能针对当前问题就会回答,无法感知历史对话。 在AI中,短期记忆通常指“对话上下文”,包括: - prompt - 对话历史 - 前序大模型推理结果 - 前序工具执行结果 短期记忆有两种存...

开源模型汇总

1. | Model | Total Params | Activated Params | Context Length | | :--------------: | :---------------: | :-------------------: | :----------------: | ...

Uv

1. 设置pip源 配置环境变量 2. 设置cache位置 3. 初始化项目并指定最低python版本 4. 全局安装tool 如果安装Python包是为了在终端任何地方运行它的命令(比如 , , 或者你提到的 ),可以使用 : - 效果: 它会为这个工具创建一个隐藏的独立环境,但把它的可执行命令软...

MCP Gateway

1. ref https://www.volcengine.com/docs/86681/1844858?lang=zh

Agentkit

1. 支持协议: AgentKit智能体运行时支持A2A、MCP、标准HTTP三种通信协议 2. agent访问方式 - 公网访问:默认访问方式。 - 私网访问:选择同地域中的任意一个VPC和子网,每个可用区支持最多选择一个子网。 3. 大模型 通过api endpoint对接的是火山方案 3.1....

Huoshan

基本概念

1. 参数大小 | 缩写 | 英文全称 | 中文含义 | 数值(科学计数法) | 对应中文单位 | | ------ | ------------ | -------- | ------------- | ----------- | | M | Million | 百万 | $10^6$ | 100...

Bedrock

1. Model catalog - Amazon Bedrock Foundation Models - Amazon Bedrock Marketplace - Bedrock Custom Model Import 1.1. 区别对比 | 特性 | Foundation Models | Ma...

Awesome Tools

:pdf论文翻译,提供中英文对照。

Claude Desktop通信过程

MCP

MCP与AI Agent

AI Agent向LLM “提供” 了它能使用的工具列表,这个过程通常通过以下两种方式实现: 1. 静态工具注册(Static Tool Registration): - 在设计 AI Agent 系统时,开发者会预先定义并注册一系列可供 Agent 调用的工具。 - 每个工具都有一个清晰的 描述 ...

MCP开发原理

1. MCP Server开发 1.1. 注意事项 - 使用stdio作为transport layer的时候,不要进行任何控制台输出 1.2. 交互过程 INITIALIZE = "initialize" - 协商协议版本 - primitives支持情况,如tools、resources、pro...

MCP概念理解

1. 基本概念 MCP(Model Context Protocol 模型上下文协议) 是一个开放的标准化协议,用于在AI模型和外部数据源、工具之间建立安全、可控的连接。它定义了AI系统如何访问和利用外部上下文信息的规范。MCP就像是AI应用程序的USB-C接口,为AI模型提供了一种标准化的方式来连...

MCP配置

1. 基本定义 - 每个MCP服务器都是一个独立json对象,以服务器名称作为key - key在MCP配置文件中以及全局配置文件和项目配置文件中必须是唯一的 - 每个MCP服务器条目对象都必须具有属性 2. local mcp server - (可选):在执行之前,先将进程的工作目录切换到指定路...

开发与测试

1. tools 1.1. stdio 启动命令 inspector调试 mcp config 1.2. SSE 启动命令 inspector调试 mcp config 1.3. streamable http mcp-server仅需修改transport即可 mcp confi需要将修改为,例如...

Prompt

概念

1. Message类型 System message 在大模型内部是每次加在了用户输入的前面。在 的大模型设计的时候,有三种不同的message 类型,这三者是有明显区别的。 - System Message:对大模型的角色进行定义,并输入一些基础的指令,包括大模型的身份、一些用于提高安全性的指令...

RAG

RAG(Retrieval-Augmented-Generation, 检索增强生成)is a process that helps AI models "look things up" before they answer, like accessing my calender or the we...

Vector Database

Embeddings用数值形式的向量,在高维空间表示数据(通常是文本等非结构化的数据)。传统的关系型数据库并不适合存储和搜索这些向量表示。 向量存储库能够使用相似度算法对相似向量进行索引和快速搜索,使得应用程序能够在给定目标向量的情况下找到相关向量。 例如,在个性化聊天机器人的案例中,用户会向生成式...

AI

Agntcy

1. OASF Open Agentic Schema Framework一个基于OCI(Open Container Initiative)的可扩展数据模型,用于描述agent的属性并确保agent的唯一标识。OASF支持描述A2A代理和MCP服务器,并且可以扩展以支持其他常用格式,例如Copil...

Claude Skills

1. 原理 claude skill是一类模块化能力组件,用于拓展 Claude的功能边界。每项技能都封装了: 1. 元数据 2. 指令说明 3. 可选配套资源(脚本、模板) Claude Agent Skills的设计哲学在于模块化与按需加载,旨在解决传统代理系统中常见的上下文冗余、性能衰减以及操...

CrewAI

1. install https://docs.crewai.com/en/installation

执行计划

1. explain和explain anayze 和生成的执行计划通常是一致的,但并不能保证完全一致。 1.1. 核心功能对比 | 对比维度 | EXPLAIN | EXPLAIN ANALYZE | | -------- | ------------- | --------------- | |...

Excalidraw使用示例

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements %%>>>text element-link:Excalidraw<<<%%shinerio's blog ^0js7...

锥型NAT和对称型NAT

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== %% Drawing %%

Mysql可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

Mysql当前读下幻读问题

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Excalidraw Data Text Elements 事务A ^skXrctjq 事务B ^eJoYKov2 insert into ord...

Mysql当前读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^yULSUGNg 事务B ^RjMMNDeM select from orders where 10 sel...

Mysql快照读下解决幻读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^b7TiLH2p 事务B ^bMLs1cuE select from orders; ^nEabCTcv i...

Postgresql不可重复读

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements 事务A ^EJKvgE8s 事务B ^if98ykqE select from orders; ^Gp5obo7a u...

A2A

协议是一项开放标准,旨在解决人工智能快速发展领域中的一个基本挑战:由不同团队构建、使用不同技术且归不同组织所有的AI Agents,如何有效通信与协作? 随着人工智能代理变得更加专业化和强大,它们在复杂任务上协同工作的需求也日益增长。想象一下,用户让其主要的人工智能助手规划一次国际旅行。这一个请求可...

AI Agent

AI Agent综述(Chatbot + Workflow + AI Agent + Function Call + MCP)

1. LLM E.G. Chat GPT, Google Gemini and claude. 1.1. Work Mechanism Human provide an input and the LLM responds with an output. > input(prompt) -> LLM...

设计模型(Work Flow & Agent)

1. 工作流模式(Predefined Workflow) 1.1. 流水线 流水线是最简单直接的工作流编排,通过编排一个顺序处理的流程,让模型逐步执行和推理。 场景示例: 文档处理系统:文档上传 → 格式转换 → 内容提取 → 语义分析 → 结果存储 1.2. 路由分发 路由分发是将输入分类(LL...

AI Code

AI Platform

Aws

Dify

1. chatbot - 核心就是对话 - 支持自定义System prompt - System prompt和Conversation opener支持jianjia变量渲染 - 支持rag 输入变量后,进行对话 2. Agent Agent相比chatbot增加了工具调用的能力 2.1. Co...

字典树(Trie)

1. 单级字典树 字典树最基础的应用——查找一个字符串是否在「字典」中出现过,也可以用来做最长前缀匹配。 如下图,每个路径代表一个字母,每个节点存储以该节点结尾的字符串是否存在,构建如下的一个字典树。 1-4-8-12,且12节点记录值为true,则代表存在这样的路径的字符串,即存在caa字符串。 ...

时间轮

时间轮(Timing Wheel)是George Varghese和Tony Lauck在1996年的论文实现的,是一种实现延迟功能(定时器)的精妙的高级算法,其算法应用范围非常广泛它在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一,在Java开发过程中常用的Dubbo、Net...

Dapr(Distributed Application Runtime)

dapr重点落在了runtime上,runtime是一个抽象概念,提供了运行时的实现,不需要开发人员操心,比如Java的runtime环境就是jvm。核心思想是模块化,通过sidecar的方式实现,然后通过本地rpc或者http调用。 1. Multi Runtime 分布式应用的需求: - 生命周...

Cloud Network

EIP分类

1. 全动态BGP(多线EIP) 云服务提供商的公网IP地址通过BGP与多个运营线直连的链路播报给多个运营商。BGP类型的带宽具备动态路由收敛能力,可靠性和抗DDos能力好,但价格相对静态BGP来说较贵。云服务提供商可以根据设定的寻路协议实时自动优化网络结构,以保持客户使用的网络持续稳定、高效。 2...

ER的作用

使用peering等方式构建的网络结构是Full Mesh,而企业路由器的网络结构是中心辐射型(星型拓扑)。

Aliyun LB介绍

1. ALB - 每个可用区占用三个ip,一个vip,两个local ip - 每个vip可以独立绑定eip - 。ALB实例的杭州可用区H发生故障时,ALB能够在短时间内停用该可用区,并继续使用其他启用的可用区提供服务。 - ALB默认开启跨AZ负载均衡,即ALB在同地域跨可用区的后端服务之间分配...

AWS LB介绍

应用程序负载均衡器(ALB)、网络负载均衡器(NLB)和网关负载均衡器(GLB)是云中使用的三类负载均衡器。 为了重定向应用程序流量 - ALB 会检查请求的内容,例如 HTTP 标头或 SSL 会话 ID - NLB 会检查 IP 地址和其他网络信息,以最佳方式重定向流量 - GLB 充当透明的网...

HW LB介绍

1. LB实例 - 可用区:选择部署的可用区列表,可用区越多,价格越贵 - 应用型和网络型:可组合选择购买应用型和网络型,组合不同,收费不同 - 网络配置: - 所属VPC - 网络类型:ipv4、ipv6,可组合选择,或都不选(只提供公网接入);选择ipv4后,可提供私网IP接入的私网负载均衡;选...

Nginx使用

1. nginx常用命令 Nginx的命令在控制台中输入就可以看到完整的命令,这里列举几个常用的命令: - nginx -s reload 向主进程发送信号,重新加载配置文件,热重启 - nginx -s reopen 重启 Nginx - nginx -s stop 快速关闭 - nginx -s...

Nginx架构与原理

1nginx默认的启动方式是多进程的方式,nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。worker的数量可以通过配置文件中的配置项控制,一般建议设置与机器cpu核数相当。 Nginx采用了事件驱动的模型,期望worke...

Aws

aws的nat网关作为vpc的一个特性,创NAT网关的时候可以选择公网还是私网。流量经过NAT网关,源地址都会转成网关的私网地址,公网NAT后面会经过internate gateway,然后将私网地址转换成公网地址。 1. 公网NAT 1.1. 性能 - 默认5 Gbps,最高100 Gbps - ...

AZURE

1. 公网NAT 只支持SNAT,不支持DNAT 1.1. 性能 - 50 Gbps - 100W - 5M PPS 1.2. other 1.2.1. 支持自定义TCP超时时间 1.2.2. 支持一组地址连续的公网地址池 支持16个EIP或/28大小的公共EIP前缀 1.2.3. 分布式VM提供服...

GCP

google的NAT网关比较特别,不是一个独立的网关设备,而是做在源计算节点上的源端NAT。Cloud NAT 是一种没有代管式中间代理的软件定义解决方案,采用,具有高可靠性、高性能和高可伸缩性。 1. 公网NAT Public NAT 网关会为使用网关创建与互联网的出站连接的每个虚拟机分配一组外部...

华为云

1. 竞争优势 不需要使用peering/er等实现跨vpc访问的能力,私网nat天然支持跨vpc网络访问。

腾讯云

1. 公网NAT 1.1. 性能 - 默认限速5 Gbps,最大可支持50 Gbps - 并发连接200万 - 10万新建 - 不占用vpc用户的私网ip 1.2. 配额 1.2.1. 网关 - 一个VPC支持3个NAT网关 - 每个NAT网关绑定最多10个EIP 1.2.2. SNAT - 单网关...

阿里云

1. 公网NAT 2. 特性 - 支持一键全通 - snat只允许修改eip - - 支持按量计费和带宽包 2.1.1. NIS(Network Intelligence Service) 支持实例级别的一键诊断,可以检测实例的配置与运行状态,并能根据诊断的异常项提供智能修复建议。诊断内容主要包括:...

Aws HyperPlane

- top完成所有包转发和包处理,一旦网络连接建立了,转发只在Top层完成 - Flow Master记录网络连接,充当Decider的缓存 - Decider实现网络逻辑,对于NAT来说就是进行会话分配 Flow Master可以主主扩展,利用率可以做得比较高,而Decider利用率相对来说较低。

Dpdk

Ovs

现在OpenVSwitch主要由三个部分组成: - ovsdb-server:OpenFlow本身被设计成网络数据包的一种处理流程,它没有考虑软件交换机的配置,例如配置QoS,关联SDN控制器等。ovsdb-server是OpenVSwitch对于OpenFlow实现的补充,它作为OpenVSwit...

发展历史

1. 转发平台发展 - 以openvswitch为代表的第一代内核态软件转发平台 - 以dpdk为代表的第二代内核态软件转发平台 - 以P4或NP为代表的第三代硬件转发平台。 第一代发展到第二代解决了内核态和用户态上下文切换代价较高的问题; 第二代发展到第三代解决了软件转发平台性能不足的问题。 第一...

云网络诞生

1. 产业竞争核心 - 芯片 - 操作系统 - 应用 2. 转发能力发展路线 虚拟交换机发展从10Gbit/s(以原始openvswitch为代表的内核态转发,使用 kernel 作为 datapath) -> 25Gbit/s(以dpdk技术为依托的内核态转发,使用用户空间作为 datapath)...

1. Vscode配置C++开发环境

1. 插件安装 C/C++ 2. 安装gcc 3. 配置C++环境 C++环境需要.vscode 文件夹下的以下三个文件共同定义 - ccppproperties.json :对C/C++扩展的设置。 - tasks.json :定义如何生成可执行文件。 - launch.json :定义如何调试可...

2. 基本语法

1. 程序入口 在 C++ 标准中, 函数是程序启动后调用的第一个用户定义函数,它有两种标准的定义形式: 1. :这种形式表示 函数不接受任何命令行参数。它向调用者返回一个整数值,用于表示程序的退出状态。通常,返回值 表示程序正常结束,非零值表示程序出现异常或错误。 2. :这种形式允许程序接收命令...

3. 命名空间

命名空间(namespace)是 C++ 中一项重要的特性,它可以将全局作用域划分为不同的部分,从而避免不同库或者不同模块之间的命名冲突。标准 C++ 库中的所有标识符(像类、函数、对象等)都被定义在了 命名空间里。 1. using namespace std 在没有使用 时,若要使用标准库中的标...

4. 变量定义与内存空间分配

C++ 中定义变量时,即使不进行初始化,内存空间通常也会被分配,不过不同类型的变量在分配内存空间的时机和默认初始值方面存在差异。 1. 局部变量(自动存储期) 局部变量一般定义在函数内部或者代码块内部,具有自动存储期。当程序执行到定义局部变量的语句时,会立即为其分配内存空间,但不会自动初始化,该内存...

5. String字符串

c++包含两种风格的字符串 - C 风格字符串 - C++ 引入的 string 类类型 1. char类型 C风格的字符串起源于C语言,并在C++中继续得到支持。字符串实际上是使用null字符\0终止的一维字符数组。因此,一个以null结尾的字符串,包含了组成字符串的字符。 2. 字符串函数 | ...

Code

Go Env

1. GOROOT与GOPATH - 环境变量表示 Go 语言的安装目录。在中,的默认值是,而在或中的默认值是,如果将Go安装在其他目录中,而需要将GOROOT的值修改为对应的目录。另外,则包含Go为我们提供的工具链,因此,应该将配置到环境变量 PATH 中,方便我们在全局中使用 Go 工具链。 -...

Go Mod

从 Go 1.11开始,Go引入了Go Modules作为官方的依赖管理解决方案。在Go Modules模式下,依赖项会被下载到目录下,命令默认会将可执行文件安装到目录下。文件位于项目根目录下,用于记录项目的模块信息和依赖项及其版本,一个典型的例子如下: 1. go mod命令 2. module ...

Go

Go命令行参数

- os.Args变量是一个字符串(string)的切片(slice) - os.Args的第一个元素,os.Args[0], 是命令本身的名字;其它的元素则是程序启动时传给它的参数

Channel

1. 声明和初始化 1.1. 仅声明(未初始化) 声明一个 int 类型的 channel,但未初始chnilpanicmake()make()ch := make(chan int)chch := make(chan int, 3)3varvar ch chan int = make(chan i...

Context

go1.7加入了一个新的标准库context,用来简化对于处理单个请求的多个goroutine之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个API调用。 fff88f">当一个上下文被取消时,它派生的所有上下文也被取消。当context生命周期结束时,可以从管道接收消息,子协...

Defer

1. 关键字 defer 用于注册延迟调用。 2. 这些调用直到return前才被执。因此,可以用来做资源清理。 3. 多个defer语句,按先进后出的方式执行。 4. defer语句中的变量,在defer声明时就决定了,语句中的fff88f">函数调用参数会在语句执行时立即求值,并保存下来。这意味...

Goto与Label

Go语言也支持label(标签)语法:分别是和 、。 - break label可以跳出label层级循环 - continue label可以从label继续下一次循环 - goto可以无条件的跳转执行的位置,但是不能跨函数,需要配合标签使用 执行结果 输出结果

函数

1. 结构体返回值 在Go语言中,函数返回结构体时,接收它的变量类型可以是值类型或指针类型 1.1. 使用值类型接收值类型返回值 - 结构体较小(如 2-3 个字段的结构体),直接复制不会有明显性能影响。 - 结构体的数据不需要在外部修改,返回值是独立的副本。 输出如下 1.2. 使用指针类型接收指...

变量赋值&结构访问

1. 取地址与解引用 是取地址符号,放到一个变量前使用就会返回相应变量的内存地址。如 是指针运算符,一个指针变量指向了一个值的内存地址。放到一个变量前可以对指针解引用,获取指针指向的实际变量。 输出如下 2. 变量赋值 go中直接对结构体进行复制,会进行值拷贝。 结构体的指针属性也会拷贝,但指针指向...

基本语法

基础

1. 变量初始化 go中使用var声明的变量会自动初始化,如下是等价的 2. array 1. 数组:是同一种数据类型的固定长度的序列。 2. 数组定义:,比如:,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。 3. 长度是数组类型的一部分,因此,和是不同的类型。 4. 数组可以通过...

并发

异常处理

在Go语言中,和都是用来处理错误情况的,但是它们的使用场景和应用场合是不同的。 1. panic 更适用于程序出现不可恢复的错误情况,例如违反了一些重要的前提条件、发生了一些严重错误等。一般来说,如果程序出现了,就意味着程序已经处于一个不可控的状态,无法继续执行下去。会导致程序立即终止并打印出错误堆...

循环与Select

1. select 语句会从上到下依次检查每个 的通信操作语句,每个case必须是一个通信操作,要么接收,要么发送。 - 如果发现某个 的通信操作可以立即执行,就会执行该 语句块并跳出 代码块。 - 如果多个 均可执行,则会随机选择一个执行。 - 如果没有任何一个 可以执行,则会执行 语句块(如果存...

类型断言

类型断言提供了访问接口值底层具体值的方式。 该语句断言接口值 保存了具体类型 ,并将其底层类型为 的值赋予变量 。若 并未保存 类型的值,该语句就会触发一个 panic。 为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。 t, ok := ...

结构体

常用Tool

1. golps golps 是LSP(Language Server Protocol)的一个语言端(Server)实现,是针对 Go 语言的LSP实现。定义了在编辑器或IDE中与语言服务器之间使用的协议,该语言服务器提供诸如自动完成,转到定义,查找所有引用等语言功能。语言服务器索引格式(LSIF...

Kafka

Redis

1. simple example 2. redis pool

Web

测试

Golang单元测试对文件名和方法名,参数都有很严格的要求。 1. 文件名必须以xxtest.go命名 2. 方法参数必须 3. 使用go test执行单元测试 在文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。 | 类型 | 格式 | 作用 | | ---- | ----------...

Benchmark

Go语言标准库内置的testing测试框架提供了基准测试(benchmark)的能力,能让我们很容易地对某一段代码进行性能测试。 本文中会对斐波那契数列进行基准测试 创建一个基准测试用例 - Benchmark测试文件必须以filename加结尾 - 方法名必须以开头 - 测试参数必须为 1. 执行...

Go性能分析

golang中性能调试优化的方法包括: - benchmark:基准测试,对特定代码的运行时间和内存信息等进行测试 - profiling: 程序分析,程序的运行画像,在程序执行期间,通过采样收集的数据对程序进行分析 - Trace:跟踪,在程序执行期间,通过采集发生的事件数据对程序进行分析 > [...

Pprof

1. 使用方式 必须在代码里引入才能使用,不像Java里jdk工具包中的 、、、、 工具可以单独使用。可以从以下两个包中引入 Golang pprof的使用方式主要有两种 1. 在程序中通过http接口的方式暴露相应的pprof的采集控制界面,需要依赖, 使用 包来进行封装。 2. 可以用来产生du...

程序调优

进程、线程与协程

1. go中线程的数量 Go 使用Goroutine 调度器 (Scheduler) 来管理Goroutine的执行。调度器的核心概念如下 1.1. GMP模型 goalng采用特有的GMP模型。 1. G(Goroutine):指的是 Go 代码中的 Goroutine。 2. M(machine...

Go面向对象

1. 自定义数据类型 增强代码可读性 2. 方法接收器 只有自定义类型(或内置类型)才能够绑定方法,从而获得面向对象的特性。例如,为 绑定 方法。,使得User对象实现了Name方法。 在Go语言中,接口的实现是隐式的,也就是说只要一个自定义类型实现了接口中声明的所有方法,那么它就被认为实现了该接口...

反射

1. 任意类型 在 Go 语言中, 常被称为“空接口”,它的确能承载fff88f">任意类型的值,但这种“万能”特性背后存在明显的权衡和局限性。也可以用关键词替代, go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息 对于这个例子, 的类型信息是 ,数据信息是 ,这两部分信息都是存储...

泛型

1. 泛型 我们知道,函数的 形参(parameter) 只是类似占位符的东西并没有具体的值,只有我们调用函数传入实参(argument) 之后才有具体的值。那么,如果我们将形参 实参这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,问题就迎刃而解:在这里我们将其称之为 类型形参(typ...

Http连接池

1. 长连接 长连接利用keep-alive技术实现,能在多次 HTTP 之间重用同一个 TCP 连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等)。 然而长连接并非没有弊端,天下没有免费的午餐,如果客户端在接收完所有的信息之后还没有关闭连接,则服务端相应的资...

Java

Java9 Java17新特性

1. 集合 增加 了 List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。 2. 私有方法 java8中允许接口有默认方法,java9中允许接口有默认私有方法 3. Optioal的ifPresentOrElse 4. 类型推断 类型...

Jni

会在指定的所有目录下查找名为 libnativememoryutils.so(Linux)或 nativememoryutils.dll(Windows)的文件。 > [!note] > 在 Linux 下,文件名必须是 libnativememoryutils.so,不能省略 lib 前缀。 1....

Mtls实现

1. 生成证书和密钥库 1.1. openssl配置 openssl中可以通过--config指定完整的配置文件,包含所有配置段(sections),用于生成包含扩展字段的证书签名请求(CSR)或自签名证书。 1.2. 生成客户端证书和服务端证书 2. 本地安装ca证书和客户端证书 windows可...

Velocity

1. 新建module-a 引入maven依赖 自定义Annotation 继承AbstractProcessor,实现自定义Processor 2. 新建module-b 添加依赖module-A 类填写注解,以生成编译后的class。可以添加些属性,用于编译class时使用。也可以给多个类添加注...

内存管理

1. 总结 java应用的内存主要分为三块 1. jvm管理的堆内存 2. jvm管理的非堆内存 3. 非jvm管理的内存 1.1. JVM管理的堆内存(Heap Memory) 存储对象实例,是 Java 内存管理的核心区域,由所有线程共享。 - 新生代(Young Generation):存放新...

虚拟线程

1. 概念 1.1. 平台线程 我们常用的Java线程与系统内核线程是一一对应的,系统内核的线程调度程序负责调度 Java线程。为了增加应用程序的性能,我们会增加越来越多的Java线程,而由于多种因素,平台线程的数量受到了很大的约束。 - 资源有限导致系统线程总量有限,进而导致与系统线程一一对应的平...

Grpc

1. 工具推荐 - apifox:rpc客户端,可以导入proto文件,自动生成rpc客户端并重试 2. 通信过程 3. 抓包 4. protocol buffers Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数...

Rpc

4+1视图

| 视图类型 | 描述 | | :-------------- | :------------------------------------------------------------------------------------------ | | 逻辑视图 | 逻辑视图面向系统逻辑分析和...

DDD

DDD理论与实践

1. 什么是DDD DDD是一种处理高度复杂领域的ff0000">设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD不是架构,而是一种架构ff0000">设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域...

核心概念

通用模型 NAT网关模型 1. 领域 汉语词典的解释:领域是从事一种专门活动或事业的范围 、部类或部门。 百度百科的解释:领域具体指一种特定的范围或区域。 两个解释有一个共同点——范围,领域的核心重点落在ff0000">”域”字上,用来确定范围的,fff88f">范围即边界 ,这也是DDD在设计中不...

Design

代码坏味道

1. 重复代码 2. 长函数 3. large class 4. Divergent Change(发散式变化) 对程序进行维护时, 如果添加修改组件, 要同时修改一个类中的多个方法,那么这就是 Divergent Change。举个汽车的例子,某个汽车厂商生产三种品牌的汽车:BMW、Benz和La...

设计原则与典型架构设计模型

架构的设计本质都是为了高内聚、低耦合 1. SOLID原则 1.1. S单一职责原则(single responsibility) 一个class应该只做一件事,一个class应该只有一个变化的原因,核心是ff0000">功能特性解耦和ff0000">高内聚性。避免一个类承担两个特性,修改A特性的时...

1. 为什么要Dpdk(Data Plane Development Kit)

1. 基于OS内核转发的劣势 1.1. 数据路径长,协议栈处理开销大 - 内核路径: 数据包需经过内核协议栈(如TCP/IP栈)的多层处理(链路层→网络层→传输层→应用层),涉及大量内存拷贝、校验和计算、上下文切换等操作。 - DPDK优化: 直接绕过内核,在用户空间处理数据包,省去协议栈的逐层解析...

ConfigMap

1. 创建 1.1. 通过kubectl命令行创建 1.1.1. --from-file参数从文件中进行创建 其中key=是可选的,默认key就是文件名,通过key=可以指定key。 1.1.2. --from-file参数从目录中进行创建 目录下每个配置文件名都被设置为key,文件的内容设成为va...

Ingress Gateway

在Kubernetes的服务网格架构中,Ingress Gateway是通过Envoy实现的。在Istio中,Ingress Gateway 是用于管理进入服务网格的流量的组件,它基于 Envoy 代理构建。Envoy 是一个高性能的代理,专门设计用于处理服务到服务的通信,支持动态服务发现、负载均衡...

Kube Proxy和Istio Envoy

- 每个节点安装了一个kube-proxy - 每个pod以sidecar的形式部署一个envoy - kube-proxy拦截的是进出kubernetes节点的流量,envoy拦截的是进出pod的流量。 - istio作为服务网格的一种实现,本质上提供了应用间的流量、安全管理和可观察性。 1. k...

Kubernetes 核心概念

kubernetes中的基本概念和术语大多是围绕资源对象来说的,而资源对象在总体上可以分为以下两类: (1)某种资源的对象,例如节点(Node),Pod,服务(Service),存储卷(Volume) (2)与资源对象相关的事物与动作,例如标签(Label)、注解(Annotation)、命名空间(...

Pod调度

k8s Master上的Scheduler服务负责实现Pod调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通过我们无法知道Pod最终会被调度到哪个节点上。 1. NodeSelector 可以实现将Pod调度到一些指定的Node上,可以...

Service

一个service对外暴露一个,client可以访问这个来实现负载均衡访问后端Pod。一个service对应的“后端”由pod的ip和端口号组成,这在kubernetes中称为endpoint。一组endpoints形如,其中和都是podIp。

Postgresql DML

1. 常用运维sql

性能优化

1. 查看数据库CPU有没有冲高,一般是有慢SQL 2. 查看数据库I/O有没有冲高,如果有一般是用了外部排序 3. 优化外部排序有两个思路,一个是添加索引,避免临时排序;第二个是限制数据规模,尽量保证在内存中完成排序 4. 先联表后排序,索引会无法利用;要做利用索引做子查询排序,然后再联表 5. ...

数据库关键配置

1. socketTimeout 未配置socketTimeout时,应用不会超时,会永远等待。当出现网络隔离故障时,服务端访问数据库的连接会hang住,导致需要等到tcp重传失败或者keepalive失败才会断开连接,故障时间会非常长。 数据库连接池中的连接可能处于如下两种状态 - 连接处于繁忙状...

数据库理论

1. 三大范式 - 第一范式(1NF) 保证列的原子性,一张表的某个列不能有多个值,不能出现重复列。实际操作中我们可能会违反这个约束,如将列值存成一个json字典,但是我们一定要保证这些值至少是一起读或写的,不存在经常部分更新的场景。 - 第二范式(2NF): 取消部分依赖,表中的每个字段都与主键相...

Etcd

Etcd常见用法

1. 数据put和get 1.1. 设置key和value 1.2. 获取指定key 1.3. 获取前缀下所有key 2. 数据过期与续约 2.1. 创建租约并设置 TTL(Time To Live) 创建一个 TTL 为 60 秒的租约: 该命令会返回一个租约ID,例如 2.2. 查看所有leas...

Etcd架构

etcd 集群通过Raft算法实现了 “动态主从 + 分布式共识” 的架构,确保数据强一致性和高可用性。etcd 集群在同一时刻只能有一个主节点(Leader),且写操作必须由该主节点处理,但读操作可以从任意节点执行。etcd集群中的节点在正常运行时通过Raft算法动态选举 “领导者(Leader)...

Watch使用与原理

Watch机制是etcd的核心功能之一,它允许客户端监听指定键或前缀的变化,实现配置变更的实时通知。默认情况下,watch是从ff0000">最新版本开始监听的。 1. 使用方式 1.1. 基本使用 添加或修改key etcd提供了多种方式使用Watch功能 1.1.1. 命令行方式 1.1.2. ...

数据存储

1. 数据存储格式 etcd的数据存储格式主要是基于键值对的形式。 - 键:是一个唯一的字符串,用于标识存储的数据,类似于字典中的键。例如,在一个配置管理系统中,键可能是某个服务的名称加上配置项的名称,如。 - 值:可以是任意类型的数据,如字符串、整数、JSON 对象等,这取决于具体的应用场景。例如...

Kafka Stream

1. 流式计算和批计算 1.1. 流式计算 流式计算是持续地从数据源获取数据,并实时地对数据进行处理和分析。 1.1.1. 特点 - 实时性强,能够在数据到达时立即进行处理。 - 数据无边界,是持续产生的 - 计算模型有状态,需要维护中间状态,一般用增量计算代替全量计算 - 时延敏感,需要低延迟处理...

Kafka架构

1. topic kafka将消息以topic为单位进行分类,一个topic就是一个逻辑队列。类比于数据库中的分库。 2. partition 为了实现扩展性,提高并发能力,kafka将一个逻辑队列(topic)划分为多个partition,每个partition保存一个topic中的部分数据,每一...

Kafka核心配置项

这里我们以网络服务用的最多的kafka组件为例,来介绍下其实现原理,已经如何在知晓原理的情况下用好kafka。 1. producer - : 控制producer端开启幂等和事务能力。开启幂等后能够保证消息不重复。 - 为生产者提供了一个唯一的事务标识符。通过这个标识符,Kafka 能够跟踪和管理...

Kafka生产消息

1. kafka获取partition对应节点 通过元数据获取 - Kafka producer 在启动时会向 Kafka 集群中的任意一个 broker(通常是配置中的 bootstrap.servers 列表中的一个)发送元数据请求(Metadata Request)。这个请求用于获取集群的元数...

Kafka高可靠

1. 生产可靠 为保证Producer发送的数据,能可靠地发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送ACK(ACKnowledge 确认收到)。如果Producer收到ACK,就会进行下一轮的发送,否则重新发送数据。 1....

Kafka高性能读写

核心: - 顺序读写 - page cache,避免每次读取磁盘。也正是因为顺序读写,避免了大量page cache miss,可以充分利用page cache。 - 零拷贝。 1. 存储结构 一个topic下多个partition,每个partition使用一个文件夹存储。partition命名规...

不同消息队列对比

- RocketMQ 的存储结构相对较为灵活,可以针对不同的场景进行优化。它采用了单一的 commitlog 文件来顺序存储所有消息,然后通过索引文件快速定位特定topic的消息。这种设计使得在处理大量 topic 时,存储资源的分配更加高效。每个topic都对应独立索引文件,加上page cach...

云时代的消息中间件

消息队列核心

- 为什么需要消息队列 - 消息队列的核心设计和使用 - 深入消息队列高性能原理 cap原则 1. 为什么需用消息队列 1.1. 业务解耦 业务解耦,代码解耦。现在的云网络服务大都遵循SDN设计思想,将服务拆分成管控转三部分,不同组件负责承载不同的业务,其解耦方式之一就是通过消息队列。很多消息队列都...

Middleware

Redis

Redis关键参数

1. go-redis 在go- redis中,连接设置是通过 Options 结构体来管理的。 1.1. 单机模式 - Network - 网络类型,可以是 tcp 或 unix,默认值为 tcp。 - Addr - Redis 服务器的 host:port 地址。 - Dialer - 创建新网...

Redis命令

1. redis支持数据类型 - 字符串 - hash(key-value) - list(有序列表) - set(无序唯一集合) - zset(有序唯一优先级队列,每个元素关联一个分数,按从小到大排列) - Bitmap - HyperLogLog,占用内存很小(12kb)的情况,可以用于估算接近...

Redis批处理

1. pipeline Redis执行一条命令需要经历以下过程:、、、。由于Redis本身是基于协议(停等机制)的,虽然Redis已经提供了像 、 这种批量的命令,但是如果某些操作根本就不支持或没有批量的操作或者需要连续执行好几个不同命令,那我们就只能一条一条地执行命令,每执行一条命令都要消耗请求与...

Redis高可用

1. master和slave数据复制机制 在Redis集群模式下,Master和Slave之间默认采用异步复制 - 客户端向Master写入数据后,Master立即返回成功响应,无需等待Slave确认。 - 数据随后异步复制到 Slave 节点。 这种设计的目的是保证高性能和可用性,但存在数据丢失...

代码示例

发布订阅模式

Redis发布订阅简介 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis的SUBSCRIBE 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。 作为例子, 下...

Zookeeper

分布式锁

1. 分布式公平锁 ZooKeeper的临时顺序节点,天生就有一副实现分布式锁的胚子。 1. ZooKeeper的每一个节点,都是一个天然的顺序发号器。 在每一个节点下面创建临时顺序节点(EPHEMERAL\SEQUENTIAL)类型,新的子节点后面,会加上一个次序编号,而这个生成的次序编号,是上一...

基本概念

1. zookeeper数据存储 ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如 /hbase/master,其中 hbase 和 master 都是 ZNode。每个 ZNode 上都会保存自己的数据内容,同时...

DCN

DCN架构演进

1. 服务与网络三大耦合问题 - 交换机堆叠,存在单点风险和运维困难问题 - 物理网络大二层隧道,引入更大故障域 - 硬件墙的引入限制整体云业务规模,硬件强性能不行,无法横向扩展 2. 名词解释 NSA(Network Service Area) - NWS(Network Service): 网络...

交换机堆叠

1. 背景 主机连接单个交换机,存在单点故障问题(单个交换机故障),解决tor交换机单点故障的思路有两个。 - 上层服务(计算、网络)保证高可用,当一个tor下主机故障时,可以快速在另一台tor下拉起新主机 - 两台tor交换机堆叠,保证tor交换机的高可靠 2. 概念 交换机堆叠一般是指被背板堆叠...

大二层架构的问题

1. 历史背景 网关在汇聚上的原因是因为openstack平面约束,要求资源池里面的节点必须在一个二层上。 管理服务区解大二层原因是管理区容灾1.0方案依赖vip在az间漂移,管理区汇聚下就是一个二层。同时,az之间还需要在两个az的汇聚交换机上通过硬件vxlan打一个二层隧道。 2. 大二层的问题...

(七)Linux下实现NAT

在NAT Overview一文中,我们介绍了在linux下通过iptables实现NAT功能的命令及原理。本文以容器为例,进行几个简单的实验来进一步了解linux下NAT的实现过程和应用。 1. nat在容器中的应用 1.1. SNAT linux下一个最典型的NAT应用就是docker容器借助宿主...

(九)高性能NAT

(五)NAT ALG

--- title: NAT ALG date: 2023-02-01 categories: - network tags: - network - nat --- 能够对特定的应用层协议进行转换,在对这些特定的应用层协议进行转换过程中,通过的会话信息来改变封装在报文载荷中的和端口信息,最终实现下...

(八)高可靠NAT

1. 双机冷备 2. 双机热备 双机热备可以极大地提升NAT服务的可靠性和承载能力,但是两台NAT设备上的NAT配置也需要完全相同。这样就会出现一个问题:如果两个NAT设备分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了优先级的概念。 在双机热备的环境...

(六)路由器配置NAT

本章节主要以cisco路由器配置为例,使用GNS3仿真平台搭建一个最简单的实验环境进行验证。 1. 常用命令(cisco) 首先介绍本实验可能用到的一些命令,先有个概念,后面再在实验中熟悉并掌握。 1.1. VPC 1.2. 路由器 2. 环境 2.1. 配置PC 2.1.1. PC1 2.1.2....

(十)NAT穿透

NAT高可靠

1. 双机热备 双机热备对于防火墙来说是一个必不可少的功能,两台防火墙上的NAT配置也需要完全相同,这样就会出现一个问题:如果两个防火墙分别将两条不同的流映射到相同的公网地址,并且端口也相同的话,势必会造成表项的混乱,所以我们引入了地址池优先级的概念。 在双机热备的环境中,如果地址池被配置为高优先级...

流日志

1. 格式 1.1. 网络信息7元组 internalip,internalport,externalip,externalport,transitip,transitport,protocol 1.2. 流量数据 packetnum,bytesize 1.3. 时间信息 starttime,end...

Network

IP分片报文和TCP分段报文

分组可以发生在传输层和网络层,传输层中的TCP会分段,网络层中的IP会分片。IP层的分片更多的是为传输层的UDP服务的,由于TCP自己会避免IP的分片,所以使用TCP传输在IP层都不会发生分片的现象。我们在学习TCP/IP协议时都知道,TCP报文段如果很长的话,会在发送时发生分段,在接受时进行重组,...

SCTP

SCTP ( Stream Control Transmission Protocol ),即流媒体控制传输协议,是一种可靠的基于无连接数据包网络如IP网络之上传输协议。他被设计用来在IP网络上传输PSTN在窄带信令消息,同时也能支持宽带信令消息的传输。 SCTP可以看作OSI层次结构中的传输层,它...

Tcp KeepAlive

1. 起源 连接有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接。长连接环境下,双方建立交互的连接并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现: 1. 主机突然掉电、死机、异常重启 2. 中间路由...

Tcp重传

1. tcp超时重传 重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的确认应答报文,就会重发该数据,也就是我们常说的超时重传。 TCP 会在以下两种情况发生超时重传: - 数据包丢失 - 确认应答丢失 1.1. RTT与RTO - RTT(Roud Tri...

Nat封装Pp协议

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== Text Elements client ^pdsoIL0z NAT ^lEtOOR5s Server ^7HyKnicB SYN seq0 ^3...

源地址透传

1. TOA(TCP Option Address) TOA将源地址放在字段中。option字段最长40字节,每个选项由三部分组成:op-kind、op-length、op-data,我们最常见的MSS字段就是在option里。目前option使用的op-kind并不多,我们只需要构建一个不冲突的o...

DHCP协议

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,统一使用两个IANA分配的端口:67(服务器端),68(客户端),DHCP客户端使用的源端口号为68,目的端口号为67发送请求消息到...

Dns Zone

DNS Zone是指域名系统(DNS)中管理的一部分域空间,包含了该区域内所有域名的记录信息。它是确保网络中计算机能够通过域名解析到正确 IP 地址的关键组成部分,对于维持互联网的正常运行至关重要。一个DNS Zone通常由一个组织或个人负责维护,这个区域包括了域名及其子域的权威信息,如IP地址、邮...

DNS

DNS配置与匹配规则

1. multi dns ip 对于一个主机配置多个DNS IP 1.1. 作用 1.1.1. 冗余和容错 - 当第一个DNS服务器无法响应时,主机会自动尝试使用第二个、第三个DNS IP - 提高DNS解析的可靠性,即使一个DNS服务器宕机,仍可通过其他DNS服务器进行域名解析 1.1.2. 负载...

Https抓包

由于主流浏览器都只支持HTTP/2 Over TLS,也就是说当前HTTP/2网站都使用了HTTPS,数据传输都经过了SSL加密,常规抓包方法并不能看到明文数据。 1. wireshark Wireshark 的抓包原理是直接读取并分析网卡数据,要想让它解密 HTTPS 流量,有两个办法: 1)如果...

HTTP协议解析

1. HTTP 1.0 !http1.0抓包.pcapng HTTP/1.0 默认为每一对 HTTP 请求/响应都打开一个单独的 TCP 连接。 - 17945-17947:tcp三次握手 - 17948:Server告诉Client更新自己的接收窗口大小 - 17949:Client发起HTTP ...

TLS

1. TLS是什么 Transport Layer Security (TLS) 是一种被广泛采用的安全协议,旨在增强互联网通信的私密性和数据安全性。TLS的主要使用场景是对Web应用和服务器之间的通信(例如,Web 浏览器加载网站)进行加密。TLS 还可以用于加密其他通信,如电子邮件、消息传递和 ...

常用Http Header

1. remoteaddr 表示发出请求的远程主机的IP地址,remoteaddr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时 1. 假设中间没有任何代理,那么网站的web(,Apache等)就会把remoteaddr设为你的机器IP 2...

应用层

ARP

1. arp响应的条件 1.1. 普通主机响应 ARP 请求的条件 当本机是普通主机时,只有当 ARP 请求中的目标IP地址与本机IP地址匹配时,普通主机才会发送ARP响应。例如,主机A的IP地址为192.168.1.10,当它接收到ARP请求,且请求的目标 IP地址为192.168.1.10时,主...

VLAN

1. 介绍 当网络上所有设备在同一个广播域产生大量的广播和多播帧(arp/dhcp/stp/rip),就会与业务数据流争带宽,造成网络性能恶化。将大型广播域分段是提高网络性能的方法之一。路由器能够将广播包阻隔在一个三层接口上,但是路由器的LAN接口数量有限,它的主要功能是在三层网络间传输数据,而不是...

交换机原理

1. SAT(MAC地址表、FDB、CAM) 交换机的Source Address Table(源地址表),也称为MAC地址表、FDB(forwarding database)或CAM(Content Addressable Memory),是一种用于存储和管理设备MAC地址和端口之间映射关系的表格...

概念解释

ISP: Internet Service Provider 互联网服务提供商 IXP: Internet eXchange Point 互联网交换点 通信方式: - 客户-服务器方式(C/S) - 对等方式(P2P) 1. 电路交换 电路交换必须经过“建立连接(占用 通信资源)一通话(一直占用通信...

IPSec

IPSec (Internet Protocol Security) 协议主要工作在OSI模型的第3层网络层,为上层传输层协议(如TCP、UDP等)提供安全支持。IPSec不是一个单独网络协议,而是一系列为IP网络提供安全性的协议和服务的集合,提供了认证、加密、数据完整性等安全服务。 1. 主要协议...

仿真

Ipv4地址

1. 特殊ip地址汇总 - 0.0.0.0/8:用于广播信息到当前主机 - 10.0.0.0/8:用于专用网络中的本地通信 - 172.16.0.0/12:用于专用网络中的本地通信 - 192.168.0.0/16:用于专用网络中的本地通信 - 100.64.0.0/10:用于在电信级NAT环境中服...

IPv6地址

1. 地址格式 IPv6地址有128bit,在这128bit中,前64bit是网络前缀,后64bit是接口标识。在前64bit中,前48bit又是全球可汇总地址,在给一个公司分配IPv6地址时,总是分配给它一个前48bit固定的地址,而后面的16bit又可以被该公司用来做子网地址。这样分配,可以方便...

网络层

链路本地地址

链路本地地址(Link-local Address)是一类特殊的IP地址,仅用于在网段内、同一广播域内的主机相互通信使用,这类主机可认为是不需要外部互联网服务的。其中IPv4的链路本地地址定义在169.254.0.0/16范围内,IPv6定义在fe80::/10范围。链路本地地址在ipv4和ipv6...

BGP

BGP基础概念

1. AS - OSPF、IS-IS等IGP路由协议在组织机构网络内部广泛应用,随着网络规模扩大,网络中路由数量不断增长,IGP已无法管理大规模网络,AS的概念由此诞生。 - AS指的是在同一个组织管理下,使用统一选路策略的设备集合。 > [!question] 不同的AS之间需要进行通信,在AS之...

BGP邻居建立

- 先启动BGP的一端先发起TCP连接,R1先启动BGP,R1使用随机端口号向R2的179端口发起TCP连接,完成TCP连接的建立。 - 三次握手建立完成之后,R1、R2之间相互发送Open报文,携带参数用于对等体建立 - 参数协商正常之后双方相互发送Keepalive报文,收到对端发送的Keepa...

路由协议

CPU

1. 基本概念 - Socket: 指的是主板上用于安装物理CPU芯片的接口。每一个这样的接口能够插入一个物理的CPU处理器,一台计算机可以配备多个这样的插槽,进而安装多个物理CPU,以增强系统的计算能力和性能。一个物理CPU可以有多个物理CPU核。 - 物理核(): 可以看的到的,真实的cpu核,...

File System

常见文件目录

各大发行版基本一致,遵循的是通用的FHS(Filesystem Hierarchy Standard)约定,(个别子目录如 lib64、multiarch 名称可能略有不同) 1. 开发者部署模板 用一个假想应用myapp为例,展示其在Linux系统中应该如何分布文件和目录。 🏗️ myapp 部...

Linux

Bridge

1. 常见命令 2. 泛洪 - 泛洪机制:桥接设备会将该数据包从除接收端口之外的所有其他端口发送出去。这样做的目的是确保目的设备无论连接到哪个端口,都有机会接收到这个数据包。例如,在一个包含多个主机的局域网中,如果主机 A 向一个桥接设备发送了一个数据包,而该桥接设备的 FDB 表中没有目的主机的 ...

Geneve隧道配置

1. geneve协议格式 GENEVE与VXLAN类似,仍然是Ethernet over UDP,也就是用UDP封装Ethernet。VXLAN header是固定长度的(8个字节,其中包含24bit VNI),与VXLAN不同的是,GENEVE header中增加了TLV(Type-Length...

Network Namespace基础

可以创建一个完全隔离的新网络环境,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables等等。总之,与网络有关的组件都是独立的。 可以看到我们刚才创建的网络环境 进入虚拟网络环境,使用命令 只能看到lo口 这样我们可以在新的网络环境中打开一个shell。 连...

Route Table与VRF

在 Linux 系统中,支持使用多个 route table(路由表) 是为了增强网络配置的灵活性和功能性。多路由表的主要作用如下: 1. 实现策略路由(Policy Routing) 默认情况下,Linux 使用主路由表()中的路由规则来决定流量的转发路径。然而,某些场景下可能需要基于流量的源地址...

Tcp关键内核参数

1. tcpsynretries 这个参数值设置的是client发送SYN如果server端不回复的话,ff0000">重传SYN的次数。对我们的直接影响就是connet建立连接时的超时时间。当然Java通过一些C原生系统调用的组合使得我们可以进行超时时间的设置。在Linux里面默认设置是5,下面给...

Tun Tap介绍

在计算机网络中,TUN(Tunnel)与TAP(Test Access Point)是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。 - TAP等同于一个以太网设备,它操作第二层数据包...

Vxlan隧道配置

1. 主机应用通过绑定独立路由表或vrf的方式通过隧道访问对端 > 图为网图,本文配置vtep地址换成了10.0.0.250和10.0.0.251 250主机配置 查看当前系统信息 > 虽然从概念上讲,VRF 主要用于实现逻辑上的路由隔离,但在系统的网络配置和管理中,它被呈现为一个类似普通网络接口的...

管道

1. client 2. server 3. ouput 数据直接在内存中的管道缓冲区传输

网卡混杂

网卡在混杂模式下不会校验收到的报文的mac和ip

CPU使用率

1. 查看进程CPU使用率 1.1. 平均使用率 ps命令显示的值是进程的平均CPU使用率,计算公式为: $\%CPU=(进程使用的CPU时间​/进程运行的总时间)×100$ 如下命令显示CPU占用最高的前几名进程 输出如下 1.2. 瞬时使用率 如果想监控某个进程(比如 )的 CPU 使用率 ...

Ls

1. 基于文件名排序 2. 基于文件大小排序 3. 基于文件时间排序

Lsof

(List Open Files)是一个用于列出系统中打开文件的工具。这里的 “文件” 是一个广义的概念,包括常规文件、目录、块设备、字符设备、共享库、网络套接字等。会列出系统中当前用户打开的所有文件。 : 列出指定进程 ID 的进程打开的文件 :列出指定用户打开的文件 : 列出正在执行指定命令的进...

PS

命令是process status的简称,用于显示当前运行的进程的信息。在不使用任何标识的情况下,会显示所有当前用户启动的进程,比如: - PID: 进程的ID号 - TTY: 终端名称缩写 - TIME: CPU时间,即进程使用CPU的总时间 - CMD: 所执行的命令名称。 1. 参数 命令支持...

Sort

-f :忽略大小写的差异,例如 A 与 a 视为编码相同; -b :忽略最前面的空格符部分; -M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法; -n :使用『纯数字』进行排序(默认是以文字型态来排序的); -r :反向排序; -u :就是 uniq ,去重,相同的数据中,仅出现一...

常用运维命令

核隔离和Cgroup

!CPU1. 基本概念 1. 核隔离 修改内核启动参数 1.1. CPU加压测试 htop可以看到隔离的核没有受到影响 2. 绑定进程到隔离核心 3. cgroup 输出如下 > [!info] > Worker 0 started on CPU 2 Worker 2 started on CPU ...

Numa架构

是一种多处理器架构,它将系统中的内存分成多个节点,并将每个节点分配给不同的处理器。在 NUMA 架构中,每个处理器可以访问本地节点的内存,但访问远程节点的内存速度较慢。因此,NUMA 架构可以提高多处理器系统的性能和可扩展性。 下图为英特尔S2600系列服务器主板 - 两个CPU插槽,CPU插槽之间...

Os

Page Cache

文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被CPU访问。由于读写硬盘的速度比读写内存要慢很多,所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux内核使用页缓存(Page Cache)机制来对文件中的数据进...

中断

1. 中断(Interrupt)的定义 中断是计算机系统中一种关键机制,允许处理器暂停当前执行的任务,转去处理更高优先级的紧急事件,处理完成后恢复原任务。中断机制提高了系统的响应速度和资源利用率,是操作系统实现多任务、实时响应的基础。 2. 硬件中断(Hardware Interrupt) vs 软...

操作系统地址空间

操作系统的地址空间是一个抽象概念,表示操作系统在运行时能够管理和访问的内存范围。它提供了一种将物理内存映射到逻辑地址的机制,使得程序能够在一个独立于物理硬件的虚拟地址空间中执行。 1. 用户空间与内核空间 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所...

操作系统概述

1. 操作系统基本特征 1.1. 并发 并发是指两个或多个事件在同一时间间隔内发生,在多道程序环境下,一段时间内宏观上有多个程序在同时执行,而在同一时刻,单处理器环境下实际上只有一个程序在执行,故微观上这些程序还是在分时的交替进行。操作系统的并发是通过分时得以实现的。操作系统的并发性是指计算机系统中...

进程调度策略

进程调度的关注点: - 性能:执行完所有任务需要的总时间。 - 公平性:每个任务都期望自己先被执行,尽早完成。 - 响应时间:首次运行时间 - 任务提交时间。 性能、公平性、响应时间是矛盾的,为了达到最好的性能,我们希望cpu不要在不同进程或线程之间切换,这样避免了上下文的切换,一个任务从开始执行到...

Openssl

1. RSA密钥 生成私钥 - :指定算法为 RSA - :指定密钥长度为 2048 位(可改为 4096 - :输出私钥文件路径 生成公钥 - :输入私钥 - :导出公钥 - :输出公钥文件路径 2. ECC密钥 生成对应的ECC私钥 - :指定椭圆曲线(可换成 , , , 等) - :生成密钥 ...

SSL证书

在密码学和数字证书体系中,一般没有“私钥证书”的说法。主要是公钥证书。公钥证书(通常就简称为数字证书)是一种电子文档,包含以下信息 - 公钥信息 - 持有者身份信息(如姓名、组织等) - 证书颁发机构(CA)的数字签名等 其主要目的是将公钥与特定的实体(如个人、服务器等)绑定,用于在网络通信等场景中...

Webauthn

在数字时代,密码已成为我们日常生活和在线活动中不可或缺的一部分。尽管互联网已经发展了20多年,许多方面都有了巨大的改进,但只有密码,还是20年前的用法。更准确的说,它的用户体验比 20 年前更差了。 1. 密码的强度现在要求越来越高,一般不能少于8个字符,还要包括特殊符号。 2. 除了密码,通常还有...

Tech

CPU架构

1. 主频 CPU 主频,即CPU的时钟频率,是衡量 CPU 运算速度的重要指标,以下从其定义、工作原理及与性能的关系等方面进行具体解释。 - 基本定义:CPU 主频表示 CPU 内数字脉冲信号振荡的速度,单位为赫兹(Hz)。通常所说的CPU 主频为2.4GHz、3.6GHz等,意味着CPU内部的时...

CPU缓存设计

1. 缓存 Cache Memory也被称为Cache,是存储器子系统的组成部分,存放着程序经常使用的指令和数据,这就是Cache的传统定义。从广义的角度上看,Cache是快设备为了缓解访问慢设备延时的预留的Buffer,从而可以在掩盖访问延时的同时,尽可能地提高数据传输率。 快和慢是一个相对概念,...

CPU调度策略

1. MLFQ(Multi-Level Feedback Queue) - 系统中存在多个就绪队列,每个队列有不同的优先级,高优先级队列中的进程会优先获得 CPU 执行。 - 进程在队列之间可以移动,根据其行为(如 CPU 占用时间、I/O 操作等)动态调整优先级。 1.1. 规则 1.1.1. 优...

Blog Online

TODO

- BGP实践,BGP PEER建立 - 核隔离与CGROUP - NUMA - 中断 - 硬件卸载 - 大页 - etcd 1. cloud wan ccn一个vpc支持创建几个attachment ccn路由同步,会同步到未关联的attachment吗?默认会同步所有路由表吗?

Icloud

使用土区账号购买50G icloud,平均每个月2.5块 1. 土区账号登录icloud 2. 国区账号登录apple store 3. 闲鱼购买土区礼品卡充值到土区账号 4. 土区账号购买icloud 5. 过去账号通过airdop发送邀请土区账号加入家庭组 6. 土区账号共享icloud设置家庭...

Excel

1. 将多个sheet join合并 Power Query自动化合并(适合多列/大数据) 1. 导入数据到Power Query: - 在Excel中点击数据 > Power Query > 获取数据,加载Sheet1和Sheet2。 2. 合并查询: - 选择Sheet1作为主表,与Sheet2...

Vnc远程桌面

总览

Clash For Linux

Jupyter安装

z从sqlite官网,下载安装最新版本sqlite3 清理旧版本的sqlite3 安装并编译python环境 配置jupyter lab 添加python环境 1. 格式转换

Movie Pilot

1. docker 安装 注意要通过环境变量配置代理,否则无法访问themoviedb或thetvdb等 2. 下载与整理 可以通过软连接目录的方式达到下载目录和mp的/media目录一致。 3. ref https://wiki.movie-pilot.org/zh/install

Movie Bot

NAS搭建

1. 主机硬件 - 主板:微星B360M Mortar - CPU:i3-8300t - 内存:金士顿骇客8G 2 DDR4 - SSD:三星980 NVMe PCIe3.0 500G - 西数:红盘4T - 散热器:利民AXP90 X47 - 电源:台达VX350 - 机箱:先马米立方matx 2...

Openwrt

Ubuntu系统备份

1. 备份 2. 还原 如果当前启动无法启动,可以通过live cd来启动并执行恢复操作

Xiaoya

windows下必须使用bridge模式 1. docker安装 2. 定时清理 3. 获取元数据 在wsl的ubuntu子系统中执行 4. 参考

Advanced Table

obsidian 功能 - 自动化格式表格 - Excel样式的表格导航,即使用Tab和Enter在行和列之间导航 - 对指定的列进行函数求值 - 添加、删除、移动行和列 - 设置列的对齐方式 - 对指定列进行排序 - 将表格导出为CSV格式 公式 基本格式如下: 如中的 代表最后一行、第二列,右边...

Appearance

打开笔记仓库的 .obsidian 文件夹,其中如果没有 snippets 文件夹则创建。在 Obsidian 中的 CSS snippets 都是以 .css 的档案格式储存在特定的文件夹。如果你有将 CSS snippets 放到该文件夹,就会在 Obsidian 显示开关。 1. 别人给你的,...

Dataview

1 查询依据 yaml数据/metainfo 2 使用查询语言 使用下列语法创建查询语言代码块 dataview query command 3 使用内联查询 内联查询的结果只能是一个,不能查询一个列表。当前页面可以通过获得,其他页面可以通过双链语法获得 通过下列语法创建内联查询: 此博客文件名: ...

Excalidraw

obsidian 是一个手工风格的白板工具。可以使用呼出命令菜单,输入excalidraw进行创作。 library 提供了很多公开的模板库可以帮助我们画出很多精美的图案。 导出 可以导出为png或svg 双链 鼠标右键选中create link,可以在excalidraw中使用双链。和在markd...

Image Auto Upload Plugin

obsidian 使用aliyun oss作为obsidian图床 1. 下载 2. 配置oss作为图床 3. 在obsidian中粘贴图片后自动上传aliyun os

Mind Map

obsidian 1. 使用方式 使用呼出命令行,输入通过提示补全命令 2. pin 可以将思维导图的预览面板嵌到当前笔记中。 3. copy screenshot 将svg格式mind map复制到剪切板 4. bug修改 mind map已经很久没有维护了,对于代码块支持存在bug,会导致无法生...

Minimal Theme Setting

obsidian 设置主题 settings -> options -> appearance里面选择主题 Style settings 自定义包括字体等各种样式 Minimal Theme setting 里面内置了一些经典的配色,可以对主题进行一些快速设置

Obsidian Tasks

任务管理 常用语法 1. 今日之前(包括)已完成,done before 2. 本周截止当前日(包括)已完成,done after 3. 根据重要性排序,sort by priority reverse 示例 获取本周所有已经完成的任务,按优先级倒序排列 tasks done before done...

Obsidian总览

obsidian 1. 视图 obsidian一共提供了三种视图: - preview mode:预览模式,可以编辑,显示实时预览结果,此模式下metadata不会渲染 - reading mode:阅读模式,markdown渲染后结果,不可编辑 - source mode:以纯文本形式显示mark...

代理

ssh代理

效率工具

1. 添加环境变量 2. windows命令行代理 3. git代理 也可以直接编辑/.gitconfig文件 4. windows配置beyondcompare作为gitdiff 修改.gitconfig配置文件 5. 删除重复文件

Vault

Tcp异常断链

1. 现象 客服访问建行业务偶现超时。 建行server端主动发起断链,发送fin报文,客户B收到后回ACK报文,进入fin-wait-2状态 客户B服务端前设置有防火墙,fin状态下设置10秒超时,客户B超时后的rst报文被防火墙丢了,建行server无法收到rst报文,导致一直处于fin-wai...

业务偶现超时

1. 现象 客户A通过NAT网关访问客户B业务偶现超时。 2. 抓包分析 中间设备抓包发现,服务端主动断链,发送FIN报文并收到ACK报文完成两次挥手,但客户端并没有随之进行被动关闭,直到约20秒后发出rst报文,在此期间连接一直处于半关闭状态。而NAT网关fin超时时间为5秒,此时会话已经老化,再...

交换机选型要点

交换机选型

clos架构

CLOS网络的核心思想是:用多个小规模、低成本的单元构建复杂,大规模的网络。简单的CLOS网络是一个三级互连架构,包含了输入级,中间级,输出级。下图中的矩形都是规模小得多的转发单元,相应的成本也很低。简单来说,CLOS就是一种多级交换架构,在输入输出增长的情况下,增加中间交换单元的数量,而不是每个交...

NAT分片报文

前文我们聊到,在NAT地址转换中,NAT除了对IP地址转换外,还使用到TCP或UDP报文的端口号、ICMP报文的ICMP头中的identifier字段信息。当一个IP分片报文和TCP分段报文 1|IP报文被分成若干片之后,这些信息只有首片报文会携带,后续分片报文依靠报文ID、分片标志位、分片偏移量依...

NAT之ICMP

报文没有类似于或的端口信息,而正常NAT为了内网的安全性和IP地址的高利用率,一般都使用了五元组来匹配NAT会话。为了对ICMP的请求报文进行NAT映射,又要确保网络的安全性,只允许合法的ICMP响应报文进入内网,就必须对其进行特殊的处理。 1. ICMP请求和应答报文 ICMP的request和r...

NAT会话

我们知道传输层的任意一条流都是通过两个建立的,由组成,因此一条流可以用五元组表示。这个五元组中的任意一个元素都不能改变,否则就是另外一条流了。对于服务端来说,和中任意一个变了,就意味着一个新的接入连接;而对于客户端来说,和中任意一个发生变化,访问的就是一个新的服务,比如通常是一个服务,是一个数据库的...

Thrift协议

是一种轻量化、语言无关的RPC框架。主要包含三大部分:代码生成、序列化框架、RPC框架。 Thrift网络协议栈 采用的是模型,网络协议栈从下到上分别为:、、、。 Transport 传输层为网络提供了一个简单的抽象,定义了具体的网络传输协议。其本质是一个提供了通过各种协议进行通信的框架,支持包括和...

shell编程

本文主要介绍shell编程的基本语法以及实际应用中的常见命令。 1. 注释 1.1. 单行注释 1.2. 多行注释 多行注释也可替换成或 2. 变量 2.1. 变量定义 使用的形式,VALUE如果是字符串的话,可以使用单引号、双引号或者不加引号。单引号内的任何字符都会原样输出,不能进行转义,单引号内...

netstat命令

是基于Netstat这个命令行工具的指令,它可以用来查询系统上的网络套接字连接情况,包括,以及。另外它还能列出路由表,接口状态和多播成员等信息。 1. 参数选项 | 参数 | 作用 | | | --- | -----------------------------------------------...

系统信息查看命令汇总

在系统下经常要查看各种信息,需要使用到各种命令,全部背下来也是一项不小的工作量。每次临时找也比较浪费时间,这里汇总下常用的命令。 1. 系统 1.1. 查看linux内核版本 文件系统不是普通的文件系统,而是系统内核的映像。也就是说,该目录中的文件是存放在系统内存之中的,它以文件系统的方式为访问系统...

数据库隔离级别

本文主要目的是阐明数据库的四种隔离级别以及在mysql下实现隔离的原理。 1. 隔离级别 数据库事务隔离级别从低到高分别是:read uncommitted、read committed、retpeatable read、serializable,依次解决了数据库脏读、不可重复读、幻读问题。 - 脏...

用户和权限

使用 Linux 系统,不免会和用户和权限打交道,本文介绍了根权限和文件的权限属性等概念和应用。 1. 用户与群组 使用查看所有用户 使用查看所有用户组 修改文档所有者或群组 2. Linux权限 命令 - 代表三种身份owner/group/other,a代表全部身份all - 代表三种操作行为(...

进程管理

在 系统中,进程是资源调度的最小单位,进程的管理关乎着你使用系统的体验。 1. 进程类型 Linux 系统里有几种不同类型的进程:用户进程(User processes)、守护进程(Deamon processes)和内核进程(Kernel processes)。 1.1. 用户进程 系统里大多数进...

NAT概览

1. 背景 地址使用4个字节进行存储,最多能够提供个地址。随着互联网尤其是物联网的发展,全球地址早已不够用,因此人们发明了(网络地址转换)来缓解这个问题。 简单来说,国际互联网组织划分了三个网络地址段作为内部网络本地通信使用,分别是,,。大部分内部机器都使用这些网段中的私有地址,如果它们需要访问公网...

NAT Overview

1. What is NAT? 1.1. NAT(Level 4) NAT(Network address translation)即网络地址转换,工作在OSI模型的三层或四层(PNAT),用于修改IP数据包中的IP地址和端口。当在专用网内部的一些主机本来已经分配到了local ip地址,但又想和I...

iptables

iptables 是 Linux 防火墙工作在用户空间的管理工具,是 netfilter/iptablesIP 数据包过滤系统是一部分,用来设置、维护和检查 Linux 内核的 IP 数据包过滤规则。 1. 四表五链 数据包在经过每个的时候会按照每个链对应的表依次进行查询匹配执行的操作,如PRERO...

tcpdump

tcpdump是一个优秀的网络分析工具,提供了强大且简单的接口。 1. options - -i any 监听所有的网卡接口,用来查看是否有网络流量 - -i eth0 只监听eth0网卡接口 - -D 显示可用的接口列表 - -n 不要解析主机名 - -nn 不要解析主机名或者端口名 - -q 显...

数据中心网络架构

0.1. 数据中心 根据维基百科释义,指用于安置计算机系统及相关部件的设施,例如电信和存储系统。数据中心是全球协作的特定设备网络,用来在internet网络基础设施上传递、加速、展示、计算和存储数据信息。 0.2. 传统数据中心网络架构 如图1所示,传统的大型数据中心网络通常采用三层架构。cisco...

应用层DNS协议

(Domain Name System)域名解析服务采用架构,是一个应用层协议。的作用是将人类可读的域名(如:www.shinerio.cc) 转换为机器可读的 IP 地址(如:1111.111.111.111)。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。协议建立...

计算机网络(九)—— 传输层

传输层架构在网络层之上,在两台计算机进程之间传输数据,常见的传输层协议包括TCP和UDP。 1. TCP 1.1. 首部格式 1.2. TCP状态机 TCP是面向连接的,在其生命周期会有各种不同状态 | 状态 | 描述 | | ------------ | ---------------------...

路由协议

路由是选择路径并将报文沿着选择的路径进行转发的过程。 1. 路由器 1.1. 路由器功能 路由器从功能上可以划分为: - 路由选择:使用路由协议(或手工静态配置的方式)获取整个网络的拓扑结构,构造、维护路由表。 - 分组转发:分组转发结构由交换结构、一组输入端口和一组输出端口构成。 1. IP分组检...

计算机网络(七)—— 网络层 —— ICMP

互联网控制消息协议(Internet Control Message Protocol,ICMP)是TCP/IP协议族的核心协议之一,用于IP协议中发送控制消息,提供可能发生在通信环境中的各种问题反馈。通过这些消息,使管理者可以对所发生的的问题作出诊断,然后采取适当的措施解决。ICMP可以简单认为是...

数据链路层概述

数据链路层使用的信道主要分为以下两种: - 点对点信道,使用一对一的点对点的通信方式 - 广播信道,使用一对多的广播通信方式,广播信道上连接的主机很多,因此必须使用专用的共享信道协议来协调这些主机的数据发送。 0.1. 数据链路 当需要在一条线路上传送数据时,除了必须有一条物理线路外,还必须有一些必...

计算机网络(三)—— 网络的基本分类

本文主要介绍计算机网络的分类以及局域网技术。 0.1. 网络分类 - 地理位置: 1. 个域网(PAN,Personal Area Network)。个域网允许设备围绕一个人进行通信。一个常见的例子是计算机通过无线网络(蓝牙)与其外围设备(显示器、键盘、鼠标。打印机)连接。 2. 局域网(LAN,L...

计算机网络(二)—— 性能指标

计算机网络的常见的性能指标有速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率。 1. 速率(bit/s或byte/s) 速率是物理层概念,指的是信道上每秒钟传输的0/1比特数量。比如下载文件中显示的7.8MB/s指的是每秒中可以传输7.8M字节的信息。 2. 带宽(bit/s) 带宽是逻辑概念...

计算机网络(一)—— 分层模型

计算机网络按照使用范围可以划分为个域网、局域网、城域网、广域网和互联网,其组成复杂,存在着大量诸如集线器、交换机、路由器、笔记本、手机等硬件。为了降低网络设计的复杂性,现阶段大部分网络都会形成一个层次栈结构,每一层都建立在其下一层的基础之上,向上一层提供特定服务。这种分层结构的概念其实和计算机领域的...

加密算法

本文主要介绍密码学中常见的两种加密算法—对称加密和非对称加密。 1. 散列(摘要)算法 在学习加密算法之前,我们先来了解一下散列算法(散列不是加密)。散列算法是通过一定方式对原文进行计算,产生一个哈希值,不管原始数据是什么样的,得到的哈希值都是固定长度的,其作用只是为了验证数据的完整性和唯一性,无法...

© 2026 shinerio. All rights reserved.